[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:
Jen Huang 2019-07-31 09:27:42 -07:00 committed by GitHub
parent f76547c9c5
commit b8e91e3cb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 228 additions and 100 deletions

View file

@ -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({

View file

@ -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;

View file

@ -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%;
}

View file

@ -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>
)}

View file

@ -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">

View file

@ -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>

View file

@ -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}

View file

@ -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', {

View file

@ -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": "スナップショット",

View file

@ -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": "快照",