mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[SR] Allow custom index pattern to be used instead of selectable list when choosing indices to restore (#41534) (#42319)
* Allow user to use custom input instead of selectable list * Small copy change, fix indices toggle link * Remove unused translation * Adjust copy and change index pattern input to combo box * Address PR feedback
This commit is contained in:
parent
f76547c9c5
commit
b8e91e3cb7
10 changed files with 228 additions and 100 deletions
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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%;
|
||||
}
|
|
@ -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<StepProps> =
|
|||
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<StepProps> =
|
|||
(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<string[]>(
|
||||
typeof restoreIndices === 'string' ? restoreIndices.split(',') : []
|
||||
);
|
||||
|
||||
// State for setting renaming indices patterns
|
||||
const [isRenamingIndices, setIsRenamingIndices] = useState<boolean>(
|
||||
Boolean(renamePattern || renameReplacement)
|
||||
|
@ -147,7 +163,10 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
updateRestoreSettings({ indices: undefined });
|
||||
} else {
|
||||
updateRestoreSettings({
|
||||
indices: [...(cachedRestoreSettings.indices || [])],
|
||||
indices:
|
||||
selectIndicesMode === 'custom'
|
||||
? restoreIndexPatterns.join(',')
|
||||
: [...(cachedRestoreSettings.indices || [])],
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
@ -156,89 +175,163 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent<StepProps> =
|
|||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
className="snapshotRestore__restoreForm__stepLogistics__indicesFieldWrapper"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesLabel"
|
||||
defaultMessage="Select indices"
|
||||
/>
|
||||
selectIndicesMode === 'list' ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesLabel"
|
||||
defaultMessage="Select indices"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
setSelectIndicesMode('custom');
|
||||
updateRestoreSettings({ indices: restoreIndexPatterns.join(',') });
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleCustomLink"
|
||||
defaultMessage="Use index patterns"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternLabel"
|
||||
defaultMessage="Index patterns"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
setSelectIndicesMode('list');
|
||||
updateRestoreSettings({ indices: cachedRestoreSettings.indices });
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.indicesToggleListLink"
|
||||
defaultMessage="Select indices"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesHelpText"
|
||||
defaultMessage="{count} {count, plural, one {index} other {indices}} will be restored. {selectOrDeselectAllLink}"
|
||||
values={{
|
||||
count: restoreIndices && restoreIndices.length,
|
||||
selectOrDeselectAllLink:
|
||||
restoreIndices && restoreIndices.length > 0 ? (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
indicesOptions.forEach((option: Option) => {
|
||||
option.checked = undefined;
|
||||
});
|
||||
updateRestoreSettings({ indices: [] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.deselectAllIndicesLink"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
indicesOptions.forEach((option: Option) => {
|
||||
option.checked = 'on';
|
||||
});
|
||||
updateRestoreSettings({ indices: [...snapshotIndices] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [...snapshotIndices],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectAllIndicesLink"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
selectIndicesMode === 'list' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectIndicesHelpText"
|
||||
defaultMessage="{count} {count, plural, one {index} other {indices}} will be restored. {selectOrDeselectAllLink}"
|
||||
values={{
|
||||
count: restoreIndices && restoreIndices.length,
|
||||
selectOrDeselectAllLink:
|
||||
restoreIndices && restoreIndices.length > 0 ? (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
indicesOptions.forEach((option: Option) => {
|
||||
option.checked = undefined;
|
||||
});
|
||||
updateRestoreSettings({ indices: [] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.deselectAllIndicesLink"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
indicesOptions.forEach((option: Option) => {
|
||||
option.checked = 'on';
|
||||
});
|
||||
updateRestoreSettings({ indices: [...snapshotIndices] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [...snapshotIndices],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreForm.stepLogistics.selectAllIndicesLink"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
isInvalid={Boolean(errors.indices)}
|
||||
error={errors.indices}
|
||||
>
|
||||
<EuiSelectable
|
||||
allowExclusions={false}
|
||||
options={indicesOptions}
|
||||
onChange={options => {
|
||||
const newSelectedIndices: string[] = [];
|
||||
options.forEach(({ label, checked }) => {
|
||||
if (checked === 'on') {
|
||||
newSelectedIndices.push(label);
|
||||
{selectIndicesMode === 'list' ? (
|
||||
<EuiSelectable
|
||||
allowExclusions={false}
|
||||
options={indicesOptions}
|
||||
onChange={options => {
|
||||
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) => (
|
||||
<EuiPanel paddingSize="s" hasShadow={false}>
|
||||
{search}
|
||||
{list}
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
) : (
|
||||
<EuiComboBox
|
||||
options={snapshotIndices.map(index => ({ label: index }))}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.snapshotRestore.restoreForm.stepLogistics.indicesPatternPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Enter index patterns, i.e. logstash-*',
|
||||
}
|
||||
});
|
||||
setIndicesOptions(options);
|
||||
updateRestoreSettings({ indices: [...newSelectedIndices] });
|
||||
setCachedRestoreSettings({
|
||||
...cachedRestoreSettings,
|
||||
indices: [...newSelectedIndices],
|
||||
});
|
||||
}}
|
||||
searchable
|
||||
height={300}
|
||||
>
|
||||
{(list, search) => (
|
||||
<EuiPanel paddingSize="s" hasShadow={false}>
|
||||
{search}
|
||||
{list}
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
)}
|
||||
selectedOptions={restoreIndexPatterns.map(pattern => ({ label: pattern }))}
|
||||
onCreateOption={(pattern: string) => {
|
||||
if (!pattern.trim().length) {
|
||||
return;
|
||||
}
|
||||
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(','),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
|
|
|
@ -33,7 +33,7 @@ export const RestoreSnapshotStepReview: React.FunctionComponent<StepProps> = ({
|
|||
} = useAppDependencies();
|
||||
const { FormattedMessage } = i18n;
|
||||
const {
|
||||
indices,
|
||||
indices: restoreIndices,
|
||||
renamePattern,
|
||||
renameReplacement,
|
||||
partial,
|
||||
|
@ -45,7 +45,13 @@ export const RestoreSnapshotStepReview: React.FunctionComponent<StepProps> = ({
|
|||
const { index_settings: serializedIndexSettings } = serializedRestoreSettings;
|
||||
|
||||
const [isShowingFullIndicesList, setIsShowingFullIndicesList] = useState<boolean>(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 = () => (
|
||||
<Fragment>
|
||||
|
@ -82,18 +88,19 @@ export const RestoreSnapshotStepReview: React.FunctionComponent<StepProps> = ({
|
|||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{indices ? (
|
||||
{displayIndices ? (
|
||||
<EuiText>
|
||||
<ul>
|
||||
{(isShowingFullIndicesList ? indices : [...indices].splice(0, 10)).map(
|
||||
index => (
|
||||
<li key={index}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{index}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
{(isShowingFullIndicesList
|
||||
? displayIndices
|
||||
: [...displayIndices].splice(0, 10)
|
||||
).map(index => (
|
||||
<li key={index}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{index}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
))}
|
||||
{hiddenIndicesCount ? (
|
||||
<li key="hiddenIndicesCount">
|
||||
<EuiTitle size="xs">
|
||||
|
|
|
@ -293,8 +293,8 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
|
|||
<EuiSpacer size="m" />
|
||||
<EuiButton onClick={verifyRepository} color="primary" isLoading={isLoadingVerification}>
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.repositoryDetails.reverifyButtonLabel"
|
||||
defaultMessage="Re-verify repository"
|
||||
id="xpack.snapshotRestore.repositoryDetails.verifyButtonLabel"
|
||||
defaultMessage="Verify repository"
|
||||
/>
|
||||
</EuiButton>
|
||||
</Fragment>
|
||||
|
|
|
@ -121,7 +121,7 @@ export const RestoreSnapshot: React.FunctionComponent<RouteComponentProps<MatchP
|
|||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.snapshotRestore.restoreSnapshot.executeRestoreErrorTitle"
|
||||
defaultMessage="Unable to execute restore"
|
||||
defaultMessage="Unable to restore snapshot"
|
||||
/>
|
||||
}
|
||||
error={saveError}
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -9929,7 +9929,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": "スナップショット",
|
||||
|
|
|
@ -9929,7 +9929,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": "快照",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue