diff --git a/x-pack/legacy/plugins/snapshot_restore/common/lib/restore_settings_serialization.test.ts b/x-pack/legacy/plugins/snapshot_restore/common/lib/restore_settings_serialization.test.ts index 753c3d3fef34d..5d30f851906f6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/lib/restore_settings_serialization.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/lib/restore_settings_serialization.test.ts @@ -13,7 +13,7 @@ describe('restore_settings_serialization()', () => { expect(serializeRestoreSettings({})).toEqual({}); }); - it('should serialize partial restore settings', () => { + it('should serialize partial restore settings with array indices', () => { expect(serializeRestoreSettings({})).toEqual({}); expect( serializeRestoreSettings({ @@ -28,6 +28,21 @@ describe('restore_settings_serialization()', () => { }); }); + it('should serialize partial restore settings with index pattern', () => { + expect(serializeRestoreSettings({})).toEqual({}); + expect( + serializeRestoreSettings({ + indices: 'foo*,bar', + ignoreIndexSettings: ['setting1'], + partial: true, + }) + ).toEqual({ + indices: 'foo*,bar', + ignore_index_settings: ['setting1'], + partial: true, + }); + }); + it('should serialize full restore settings', () => { expect( serializeRestoreSettings({ diff --git a/x-pack/legacy/plugins/snapshot_restore/common/types/restore.ts b/x-pack/legacy/plugins/snapshot_restore/common/types/restore.ts index 4acd0b962af3b..e6e1836bc3db8 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/types/restore.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/types/restore.ts @@ -5,7 +5,7 @@ */ export interface RestoreSettings { - indices?: string[]; + indices?: string[] | string; renamePattern?: string; renameReplacement?: string; includeGlobalState?: boolean; @@ -16,7 +16,7 @@ export interface RestoreSettings { } export interface RestoreSettingsEs { - indices?: string[]; + indices?: string[] | string; rename_pattern?: string; rename_replacement?: string; include_global_state?: boolean; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/_restore_snapshot_form.scss b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/_restore_snapshot_form.scss index 884740c6f01cc..6a8f0b951c99f 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/_restore_snapshot_form.scss +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/_restore_snapshot_form.scss @@ -7,4 +7,11 @@ min-height: auto; margin-top: $euiFontSizeXS + $euiSizeS + ($euiSizeXXL / 4); } +} + +/* + * Allow toggle mode link in indices field label to be flushed right + */ +.snapshotRestore__restoreForm__stepLogistics__indicesFieldWrapper .euiFormLabel { + width: 100%; } \ No newline at end of file diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx index 6b77ddf22e73d..2ea5d54b7de3e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx @@ -17,6 +17,7 @@ import { EuiSpacer, EuiSwitch, EuiTitle, + EuiComboBox, } from '@elastic/eui'; import { Option } from '@elastic/eui/src/components/selectable/types'; import { RestoreSettings } from '../../../../../common/types'; @@ -31,10 +32,9 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = errors, }) => { const { - core: { - i18n: { FormattedMessage }, - }, + core: { i18n }, } = useAppDependencies(); + const { FormattedMessage } = i18n; const { indices: snapshotIndices, includeGlobalState: snapshotIncludeGlobalState, @@ -55,11 +55,27 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = (index): Option => ({ label: index, checked: - isAllIndices || (restoreIndices && restoreIndices.includes(index)) ? 'on' : undefined, + isAllIndices || + typeof restoreIndices === 'string' || + (Array.isArray(restoreIndices) && restoreIndices.includes(index)) + ? 'on' + : undefined, }) ) ); + // State for using selectable indices list or custom patterns + // Users with more than 100 indices will probably want to use an index pattern to select + // them instead, so we'll default to showing them the index pattern input. + const [selectIndicesMode, setSelectIndicesMode] = useState<'list' | 'custom'>( + typeof restoreIndices === 'string' || snapshotIndices.length > 100 ? 'custom' : 'list' + ); + + // State for custom patterns + const [restoreIndexPatterns, setRestoreIndexPatterns] = useState( + typeof restoreIndices === 'string' ? restoreIndices.split(',') : [] + ); + // State for setting renaming indices patterns const [isRenamingIndices, setIsRenamingIndices] = useState( Boolean(renamePattern || renameReplacement) @@ -147,7 +163,10 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = updateRestoreSettings({ indices: undefined }); } else { updateRestoreSettings({ - indices: [...(cachedRestoreSettings.indices || [])], + indices: + selectIndicesMode === 'custom' + ? restoreIndexPatterns.join(',') + : [...(cachedRestoreSettings.indices || [])], }); } }} @@ -156,89 +175,163 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = + selectIndicesMode === 'list' ? ( + + + + + + { + setSelectIndicesMode('custom'); + updateRestoreSettings({ indices: restoreIndexPatterns.join(',') }); + }} + > + + + + + ) : ( + + + + + + { + setSelectIndicesMode('list'); + updateRestoreSettings({ indices: cachedRestoreSettings.indices }); + }} + > + + + + + ) } helpText={ - 0 ? ( - { - indicesOptions.forEach((option: Option) => { - option.checked = undefined; - }); - updateRestoreSettings({ indices: [] }); - setCachedRestoreSettings({ - ...cachedRestoreSettings, - indices: [], - }); - }} - > - - - ) : ( - { - indicesOptions.forEach((option: Option) => { - option.checked = 'on'; - }); - updateRestoreSettings({ indices: [...snapshotIndices] }); - setCachedRestoreSettings({ - ...cachedRestoreSettings, - indices: [...snapshotIndices], - }); - }} - > - - - ), - }} - /> + selectIndicesMode === 'list' ? ( + 0 ? ( + { + indicesOptions.forEach((option: Option) => { + option.checked = undefined; + }); + updateRestoreSettings({ indices: [] }); + setCachedRestoreSettings({ + ...cachedRestoreSettings, + indices: [], + }); + }} + > + + + ) : ( + { + indicesOptions.forEach((option: Option) => { + option.checked = 'on'; + }); + updateRestoreSettings({ indices: [...snapshotIndices] }); + setCachedRestoreSettings({ + ...cachedRestoreSettings, + indices: [...snapshotIndices], + }); + }} + > + + + ), + }} + /> + ) : null } isInvalid={Boolean(errors.indices)} error={errors.indices} > - { - const newSelectedIndices: string[] = []; - options.forEach(({ label, checked }) => { - if (checked === 'on') { - newSelectedIndices.push(label); + {selectIndicesMode === 'list' ? ( + { + const newSelectedIndices: string[] = []; + options.forEach(({ label, checked }) => { + if (checked === 'on') { + newSelectedIndices.push(label); + } + }); + setIndicesOptions(options); + updateRestoreSettings({ indices: [...newSelectedIndices] }); + setCachedRestoreSettings({ + ...cachedRestoreSettings, + indices: [...newSelectedIndices], + }); + }} + searchable + height={300} + > + {(list, search) => ( + + {search} + {list} + + )} + + ) : ( + ({ label: index }))} + placeholder={i18n.translate( + 'xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternPlaceholder', + { + defaultMessage: 'Enter index patterns, i.e. logstash-*', + } + )} + selectedOptions={restoreIndexPatterns.map(pattern => ({ label: pattern }))} + onCreateOption={(pattern: string) => { + if (!pattern.trim().length) { + return; } - }); - setIndicesOptions(options); - updateRestoreSettings({ indices: [...newSelectedIndices] }); - setCachedRestoreSettings({ - ...cachedRestoreSettings, - indices: [...newSelectedIndices], - }); - }} - searchable - height={300} - > - {(list, search) => ( - - {search} - {list} - - )} - + const newPatterns = [...restoreIndexPatterns, pattern]; + setRestoreIndexPatterns(newPatterns); + updateRestoreSettings({ + indices: newPatterns.join(','), + }); + }} + onChange={(patterns: Array<{ label: string }>) => { + const newPatterns = patterns.map(({ label }) => label); + setRestoreIndexPatterns(newPatterns); + updateRestoreSettings({ + indices: newPatterns.join(','), + }); + }} + /> + )} )} diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx index 1ab56b798d016..65a3918d29940 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx @@ -33,7 +33,7 @@ export const RestoreSnapshotStepReview: React.FunctionComponent = ({ } = useAppDependencies(); const { FormattedMessage } = i18n; const { - indices, + indices: restoreIndices, renamePattern, renameReplacement, partial, @@ -45,7 +45,13 @@ export const RestoreSnapshotStepReview: React.FunctionComponent = ({ const { index_settings: serializedIndexSettings } = serializedRestoreSettings; const [isShowingFullIndicesList, setIsShowingFullIndicesList] = useState(false); - const hiddenIndicesCount = indices && indices.length > 10 ? indices.length - 10 : 0; + const displayIndices = restoreIndices + ? typeof restoreIndices === 'string' + ? restoreIndices.split(',') + : restoreIndices + : undefined; + const hiddenIndicesCount = + displayIndices && displayIndices.length > 10 ? displayIndices.length - 10 : 0; const renderSummaryTab = () => ( @@ -82,18 +88,19 @@ export const RestoreSnapshotStepReview: React.FunctionComponent = ({ /> - {indices ? ( + {displayIndices ? (
    - {(isShowingFullIndicesList ? indices : [...indices].splice(0, 10)).map( - index => ( -
  • - - {index} - -
  • - ) - )} + {(isShowingFullIndicesList + ? displayIndices + : [...displayIndices].splice(0, 10) + ).map(index => ( +
  • + + {index} + +
  • + ))} {hiddenIndicesCount ? (
  • diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx index 33767dcd98185..fa2d8673332ec 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx @@ -293,8 +293,8 @@ export const RepositoryDetails: React.FunctionComponent = ({ diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx index b29065bde9422..baa46855184c3 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx @@ -121,7 +121,7 @@ export const RestoreSnapshot: React.FunctionComponent } error={saveError} diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_restore.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_restore.ts index faa6a232f4022..4b9a09d39bb8b 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_restore.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_restore.ts @@ -37,6 +37,14 @@ export const validateRestore = (restoreSettings: RestoreSettings): RestoreValida }, }; + if (typeof indices === 'string' && indices.trim().length === 0) { + validation.errors.indices.push( + i18n.translate('xpack.snapshotRestore.restoreValidation.indexPatternRequiredError', { + defaultMessage: 'At least one index pattern is required.', + }) + ); + } + if (Array.isArray(indices) && indices.length === 0) { validation.errors.indices.push( i18n.translate('xpack.snapshotRestore.restoreValidation.indicesRequiredError', { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2b61a7f4ea541..497a6ab057f59 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9906,7 +9906,6 @@ "xpack.snapshotRestore.repositoryDetails.removeManagedRepositoryButtonTitle": "管理されているレポジトリは削除できません。", "xpack.snapshotRestore.repositoryDetails.repositoryNotFoundErrorMessage": "レポジトリ「{name}」は存在しません。", "xpack.snapshotRestore.repositoryDetails.repositoryTypeDocLink": "レポジトリドキュメント", - "xpack.snapshotRestore.repositoryDetails.reverifyButtonLabel": "レポジトリを再検証", "xpack.snapshotRestore.repositoryDetails.settingsTitle": "設定", "xpack.snapshotRestore.repositoryDetails.snapshotsDescription": "{count} 件の {count, plural, one {スナップショット} other {スナップショット}}が見つかりました", "xpack.snapshotRestore.repositoryDetails.snapshotsTitle": "スナップショット", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5cf839d5257e6..53fc25cc17eb2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10046,7 +10046,6 @@ "xpack.snapshotRestore.repositoryDetails.removeManagedRepositoryButtonTitle": "您无法删除托管存储库。", "xpack.snapshotRestore.repositoryDetails.repositoryNotFoundErrorMessage": "存储库“{name}”不存在。", "xpack.snapshotRestore.repositoryDetails.repositoryTypeDocLink": "存储库文档", - "xpack.snapshotRestore.repositoryDetails.reverifyButtonLabel": "重新验证存储库", "xpack.snapshotRestore.repositoryDetails.settingsTitle": "设置", "xpack.snapshotRestore.repositoryDetails.snapshotsDescription": "找到 {count} 个 {count, plural, one {快照} other {快照}}", "xpack.snapshotRestore.repositoryDetails.snapshotsTitle": "快照",