Skip to content

Commit f17ecbf

Browse files
authored
[ILM] Use global field to set the snapshot repository (#94602) (#94711)
1 parent 0c5cd74 commit f17ecbf

File tree

25 files changed

+373
-168
lines changed

25 files changed

+373
-168
lines changed

x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,46 @@ describe('<EditPolicy /> searchable snapshots', () => {
6767
expect(actions.hot.searchableSnapshotsExists()).toBeTruthy();
6868
});
6969

70+
test('should set the repository from previously defined repository', async () => {
71+
const { actions } = testBed;
72+
73+
const repository = 'myRepo';
74+
await actions.hot.setSearchableSnapshot(repository);
75+
await actions.cold.enable(true);
76+
await actions.cold.toggleSearchableSnapshot(true);
77+
await actions.frozen.enable(true);
78+
79+
await actions.savePolicy();
80+
const latestRequest = server.requests[server.requests.length - 1];
81+
expect(latestRequest.method).toBe('POST');
82+
expect(latestRequest.url).toBe('/api/index_lifecycle_management/policies');
83+
const reqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body);
84+
85+
expect(reqBody.phases.hot.actions.searchable_snapshot.snapshot_repository).toBe(repository);
86+
expect(reqBody.phases.cold.actions.searchable_snapshot.snapshot_repository).toBe(repository);
87+
expect(reqBody.phases.frozen.actions.searchable_snapshot.snapshot_repository).toBe(repository);
88+
});
89+
90+
test('should update the repository in all searchable snapshot actions', async () => {
91+
const { actions } = testBed;
92+
93+
await actions.hot.setSearchableSnapshot('myRepo');
94+
await actions.cold.enable(true);
95+
await actions.cold.toggleSearchableSnapshot(true);
96+
await actions.frozen.enable(true);
97+
98+
// We update the repository in one phase
99+
await actions.frozen.setSearchableSnapshot('changed');
100+
await actions.savePolicy();
101+
const latestRequest = server.requests[server.requests.length - 1];
102+
const reqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body);
103+
104+
// And all phases should be updated
105+
expect(reqBody.phases.hot.actions.searchable_snapshot.snapshot_repository).toBe('changed');
106+
expect(reqBody.phases.cold.actions.searchable_snapshot.snapshot_repository).toBe('changed');
107+
expect(reqBody.phases.frozen.actions.searchable_snapshot.snapshot_repository).toBe('changed');
108+
});
109+
70110
describe('on cloud', () => {
71111
describe('new policy', () => {
72112
beforeEach(async () => {
@@ -86,6 +126,7 @@ describe('<EditPolicy /> searchable snapshots', () => {
86126
const { component } = testBed;
87127
component.update();
88128
});
129+
89130
test('defaults searchable snapshot to true on cloud', async () => {
90131
const { find, actions } = testBed;
91132
await actions.cold.enable(true);
@@ -112,14 +153,17 @@ describe('<EditPolicy /> searchable snapshots', () => {
112153
const { component } = testBed;
113154
component.update();
114155
});
156+
115157
test('correctly sets snapshot repository default to "found-snapshots"', async () => {
116158
const { actions } = testBed;
117159
await actions.cold.enable(true);
118160
await actions.cold.toggleSearchableSnapshot(true);
119161
await actions.savePolicy();
120162
const latestRequest = server.requests[server.requests.length - 1];
121-
const request = JSON.parse(JSON.parse(latestRequest.requestBody).body);
122-
expect(request.phases.cold.actions.searchable_snapshot.snapshot_repository).toEqual(
163+
expect(latestRequest.method).toBe('POST');
164+
expect(latestRequest.url).toBe('/api/index_lifecycle_management/policies');
165+
const reqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body);
166+
expect(reqBody.phases.cold.actions.searchable_snapshot.snapshot_repository).toEqual(
123167
'found-snapshots'
124168
);
125169
});

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import React, { FunctionComponent } from 'react';
99
import { i18n } from '@kbn/i18n';
1010

11-
import { useConfigurationIssues } from '../../../form';
11+
import { useConfiguration } from '../../../form';
1212
import {
1313
DataTierAllocationField,
1414
SearchableSnapshotField,
@@ -29,7 +29,7 @@ const i18nTexts = {
2929
};
3030

3131
export const ColdPhase: FunctionComponent = () => {
32-
const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues();
32+
const { isUsingSearchableSnapshotInHotPhase } = useConfiguration();
3333

3434
return (
3535
<Phase phase="cold" topLevelSettings={<SearchableSnapshotField phase="cold" />}>

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,16 @@ import {
2020
import { FormattedMessage } from '@kbn/i18n/react';
2121

2222
import { useFormData } from '../../../../../../shared_imports';
23-
2423
import { i18nTexts } from '../../../i18n_texts';
25-
26-
import { usePhaseTimings } from '../../../form';
27-
28-
import { MinAgeField, SnapshotPoliciesField } from '../shared_fields';
29-
import './delete_phase.scss';
24+
import { usePhaseTimings, globalFields } from '../../../form';
3025
import { PhaseIcon } from '../../phase_icon';
26+
import { MinAgeField, SnapshotPoliciesField } from '../shared_fields';
3127
import { PhaseErrorIndicator } from '../phase/phase_error_indicator';
3228

29+
import './delete_phase.scss';
30+
3331
const formFieldPaths = {
34-
enabled: '_meta.delete.enabled',
32+
enabled: globalFields.deleteEnabled.path,
3533
};
3634

3735
export const DeletePhase: FunctionComponent = () => {

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { useFormData, SelectField, NumericField } from '../../../../../../shared
2323

2424
import { i18nTexts } from '../../../i18n_texts';
2525

26-
import { ROLLOVER_EMPTY_VALIDATION, useConfigurationIssues, UseField } from '../../../form';
26+
import { ROLLOVER_EMPTY_VALIDATION, useConfiguration, UseField } from '../../../form';
2727

2828
import { useEditPolicyContext } from '../../../edit_policy_context';
2929

@@ -47,7 +47,7 @@ export const HotPhase: FunctionComponent = () => {
4747
const [formData] = useFormData({
4848
watch: isUsingDefaultRolloverPath,
4949
});
50-
const { isUsingRollover } = useConfigurationIssues();
50+
const { isUsingRollover } = useConfiguration();
5151
const isUsingDefaultRollover: boolean = get(formData, isUsingDefaultRolloverPath);
5252
const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false);
5353

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222

2323
import { getFieldValidityAndErrorMessage } from '../../../../../../../shared_imports';
2424

25-
import { UseField, useConfigurationIssues } from '../../../../form';
25+
import { UseField, useConfiguration } from '../../../../form';
2626

2727
import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util';
2828

@@ -81,7 +81,7 @@ interface Props {
8181
}
8282

8383
export const MinAgeField: FunctionComponent<Props> = ({ phase }): React.ReactElement => {
84-
const { isUsingRollover } = useConfigurationIssues();
84+
const { isUsingRollover } = useConfiguration();
8585
return (
8686
<UseField path={`phases.${phase}.min_age`}>
8787
{(field) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
import React, { useEffect, useRef } from 'react';
8+
import { EuiComboBoxOptionOption } from '@elastic/eui';
9+
10+
import { ComboBoxField, FieldHook } from '../../../../../../../shared_imports';
11+
import { useGlobalFields } from '../../../../form';
12+
13+
interface PropsRepositoryCombobox {
14+
field: FieldHook;
15+
isLoading: boolean;
16+
repos: string[];
17+
noSuggestions: boolean;
18+
globalRepository: string;
19+
}
20+
21+
export const RepositoryComboBoxField = ({
22+
field,
23+
isLoading,
24+
repos,
25+
noSuggestions,
26+
globalRepository,
27+
}: PropsRepositoryCombobox) => {
28+
const isMounted = useRef(false);
29+
const { setValue } = field;
30+
const {
31+
searchableSnapshotRepo: { setValue: setSearchableSnapshotRepository },
32+
} = useGlobalFields();
33+
34+
useEffect(() => {
35+
// We keep our phase searchable action field in sync
36+
// with the default repository field declared globally for the policy
37+
if (isMounted.current) {
38+
setValue(Boolean(globalRepository.trim()) ? [globalRepository] : []);
39+
}
40+
isMounted.current = true;
41+
}, [setValue, globalRepository]);
42+
43+
return (
44+
<ComboBoxField
45+
field={field}
46+
fullWidth={false}
47+
euiFieldProps={{
48+
'data-test-subj': 'searchableSnapshotCombobox',
49+
options: repos.map((repo) => ({ label: repo, value: repo })),
50+
singleSelection: { asPlainText: true },
51+
isLoading,
52+
noSuggestions,
53+
onCreateOption: (newOption: string) => {
54+
setSearchableSnapshotRepository(newOption);
55+
},
56+
onChange: (options: EuiComboBoxOptionOption[]) => {
57+
if (options.length > 0) {
58+
setSearchableSnapshotRepository(options[0].label);
59+
} else {
60+
setSearchableSnapshotRepository('');
61+
}
62+
},
63+
}}
64+
/>
65+
);
66+
};

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx

Lines changed: 23 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,18 @@
55
* 2.0.
66
*/
77

8+
import React, { FunctionComponent, useState, useEffect } from 'react';
89
import { i18n } from '@kbn/i18n';
910
import { get } from 'lodash';
10-
import React, { FunctionComponent, useState, useEffect } from 'react';
1111
import { FormattedMessage } from '@kbn/i18n/react';
12-
import {
13-
EuiComboBoxOptionOption,
14-
EuiTextColor,
15-
EuiSpacer,
16-
EuiCallOut,
17-
EuiLink,
18-
} from '@elastic/eui';
19-
20-
import { ComboBoxField, useKibana, useFormData } from '../../../../../../../shared_imports';
12+
import { EuiTextColor, EuiSpacer, EuiCallOut, EuiLink } from '@elastic/eui';
2113

14+
import { useKibana, useFormData } from '../../../../../../../shared_imports';
2215
import { useEditPolicyContext } from '../../../../edit_policy_context';
23-
import { useConfigurationIssues, UseField, searchableSnapshotFields } from '../../../../form';
16+
import { useConfiguration, UseField, globalFields } from '../../../../form';
2417
import { FieldLoadingError, DescribedFormRow, LearnMoreLink } from '../../../';
2518
import { SearchableSnapshotDataProvider } from './searchable_snapshot_data_provider';
19+
import { RepositoryComboBoxField } from './repository_combobox_field';
2620

2721
import './_searchable_snapshot_field.scss';
2822

@@ -31,12 +25,6 @@ export interface Props {
3125
canBeDisabled?: boolean;
3226
}
3327

34-
/**
35-
* This repository is provisioned by Elastic Cloud and will always
36-
* exist as a "managed" repository.
37-
*/
38-
const CLOUD_DEFAULT_REPO = 'found-snapshots';
39-
4028
const geti18nTexts = (phase: Props['phase']) => ({
4129
title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotFieldTitle', {
4230
defaultMessage: 'Searchable snapshot',
@@ -71,13 +59,15 @@ export const SearchableSnapshotField: FunctionComponent<Props> = ({
7159
services: { cloud },
7260
} = useKibana();
7361
const { getUrlForApp, policy, license, isNewPolicy } = useEditPolicyContext();
74-
const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues();
62+
const { isUsingSearchableSnapshotInHotPhase } = useConfiguration();
7563

7664
const searchableSnapshotRepoPath = `phases.${phase}.actions.searchable_snapshot.snapshot_repository`;
7765

78-
const [formData] = useFormData({ watch: searchableSnapshotRepoPath });
79-
const searchableSnapshotRepo = get(formData, searchableSnapshotRepoPath);
66+
const [formData] = useFormData({
67+
watch: globalFields.searchableSnapshotRepo.path,
68+
});
8069

70+
const searchableSnapshotGlobalRepo = get(formData, globalFields.searchableSnapshotRepo.path);
8171
const isColdPhase = phase === 'cold';
8272
const isFrozenPhase = phase === 'frozen';
8373
const isColdOrFrozenPhase = isColdPhase || isFrozenPhase;
@@ -164,7 +154,10 @@ export const SearchableSnapshotField: FunctionComponent<Props> = ({
164154
/>
165155
</EuiCallOut>
166156
);
167-
} else if (searchableSnapshotRepo && !repos.includes(searchableSnapshotRepo)) {
157+
} else if (
158+
searchableSnapshotGlobalRepo &&
159+
!repos.includes(searchableSnapshotGlobalRepo)
160+
) {
168161
calloutContent = (
169162
<EuiCallOut
170163
title={i18n.translate(
@@ -201,49 +194,17 @@ export const SearchableSnapshotField: FunctionComponent<Props> = ({
201194

202195
return (
203196
<div className="ilmSearchableSnapshotField">
204-
<UseField<string>
205-
config={{
206-
...searchableSnapshotFields.snapshot_repository,
207-
defaultValue: cloud?.isCloudEnabled ? CLOUD_DEFAULT_REPO : undefined,
208-
}}
197+
<UseField
209198
path={searchableSnapshotRepoPath}
210-
>
211-
{(field) => {
212-
const singleSelectionArray: [selectedSnapshot?: string] = field.value
213-
? [field.value]
214-
: [];
215-
216-
return (
217-
<ComboBoxField
218-
field={
219-
{
220-
...field,
221-
value: singleSelectionArray,
222-
} as any
223-
}
224-
label={field.label}
225-
fullWidth={false}
226-
euiFieldProps={{
227-
'data-test-subj': 'searchableSnapshotCombobox',
228-
options: repos.map((repo) => ({ label: repo, value: repo })),
229-
singleSelection: { asPlainText: true },
230-
isLoading,
231-
noSuggestions: !!(error || repos.length === 0),
232-
onCreateOption: (newOption: string) => {
233-
field.setValue(newOption);
234-
},
235-
onChange: (options: EuiComboBoxOptionOption[]) => {
236-
if (options.length > 0) {
237-
field.setValue(options[0].label);
238-
} else {
239-
field.setValue('');
240-
}
241-
},
242-
}}
243-
/>
244-
);
199+
defaultValue={!!searchableSnapshotGlobalRepo ? [searchableSnapshotGlobalRepo] : []}
200+
component={RepositoryComboBoxField}
201+
componentProps={{
202+
globalRepository: searchableSnapshotGlobalRepo,
203+
isLoading,
204+
repos,
205+
noSuggestions: !!(error || repos.length === 0),
245206
}}
246-
</UseField>
207+
/>
247208
{calloutContent && (
248209
<>
249210
<EuiSpacer size="s" />

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import React, { FunctionComponent } from 'react';
99
import { i18n } from '@kbn/i18n';
1010

11-
import { useConfigurationIssues } from '../../../form';
11+
import { useConfiguration } from '../../../form';
1212

1313
import {
1414
ForcemergeField,
@@ -30,7 +30,7 @@ const i18nTexts = {
3030
};
3131

3232
export const WarmPhase: FunctionComponent = () => {
33-
const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues();
33+
const { isUsingSearchableSnapshotInHotPhase } = useConfiguration();
3434

3535
return (
3636
<Phase phase="warm">

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/timeline/timeline.container.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useFormData } from '../../../../../shared_imports';
1111

1212
import { formDataToAbsoluteTimings } from '../../lib';
1313

14-
import { useConfigurationIssues } from '../../form';
14+
import { useConfiguration } from '../../form';
1515

1616
import { FormInternal } from '../../types';
1717

@@ -20,7 +20,7 @@ import { Timeline as ViewComponent } from './timeline';
2020
export const Timeline: FunctionComponent = () => {
2121
const [formData] = useFormData<FormInternal>();
2222
const timings = formDataToAbsoluteTimings(formData);
23-
const { isUsingRollover } = useConfigurationIssues();
23+
const { isUsingRollover } = useConfiguration();
2424
return (
2525
<ViewComponent
2626
hotPhaseMinAge={timings.hot.min_age}

x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ export const ROLLOVER_FORM_PATHS = {
1818
maxAge: 'phases.hot.actions.rollover.max_age',
1919
maxSize: 'phases.hot.actions.rollover.max_size',
2020
};
21+
22+
/**
23+
* This repository is provisioned by Elastic Cloud and will always
24+
* exist as a "managed" repository.
25+
*/
26+
export const CLOUD_DEFAULT_REPO = 'found-snapshots';

0 commit comments

Comments
 (0)