Skip to content

Commit a537781

Browse files
authored
[Index Management] Allow to create a custom template from fleet with predefined values (#175347)
1 parent 3471a0a commit a537781

File tree

7 files changed

+240
-67
lines changed

7 files changed

+240
-67
lines changed

x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_create/component_template_create.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
* 2.0.
66
*/
77

8-
import React, { useState, useEffect } from 'react';
8+
import React, { useState, useEffect, useMemo } from 'react';
99
import { RouteComponentProps } from 'react-router-dom';
1010
import { FormattedMessage } from '@kbn/i18n-react';
1111
import { EuiPageSection, EuiSpacer, EuiPageHeader } from '@elastic/eui';
1212

13+
import { useRedirectPath } from '../../../../hooks/redirect_path';
1314
import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs';
1415
import { ComponentTemplateDeserialized } from '../../shared_imports';
1516
import { useComponentTemplatesContext } from '../../component_templates_context';
1617
import { ComponentTemplateForm } from '../component_template_form';
18+
import { useStepFromQueryString } from '../use_step_from_query_string';
19+
import { useDatastreamsRollover } from '../component_template_datastreams_rollover/use_datastreams_rollover';
20+
import { MANAGED_BY_FLEET } from '../../constants';
1721

1822
interface Props {
1923
/**
@@ -28,8 +32,35 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
2832
}) => {
2933
const [isSaving, setIsSaving] = useState<boolean>(false);
3034
const [saveError, setSaveError] = useState<any>(null);
35+
const redirectTo = useRedirectPath(history);
36+
37+
const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history);
38+
39+
const locationSearchParams = useMemo(() => {
40+
return new URLSearchParams(history.location.search);
41+
}, [history.location.search]);
42+
43+
const defaultValue = useMemo(() => {
44+
if (sourceComponentTemplate) {
45+
return sourceComponentTemplate;
46+
}
47+
48+
const name = locationSearchParams.get('name') ?? '';
49+
const managedBy = locationSearchParams.get('managed_by');
50+
51+
return {
52+
name,
53+
template: {},
54+
_meta: managedBy ? { managed_by: managedBy } : {},
55+
_kbnMeta: {
56+
usedBy: [],
57+
isManaged: false,
58+
},
59+
};
60+
}, [locationSearchParams, sourceComponentTemplate]);
3161

3262
const { api } = useComponentTemplatesContext();
63+
const { showDatastreamRolloverModal } = useDatastreamsRollover();
3364

3465
const onSave = async (componentTemplate: ComponentTemplateDeserialized) => {
3566
const { name } = componentTemplate;
@@ -45,10 +76,11 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
4576
setSaveError(error);
4677
return;
4778
}
79+
if (componentTemplate._meta?.managed_by === MANAGED_BY_FLEET) {
80+
await showDatastreamRolloverModal(componentTemplate.name);
81+
}
4882

49-
history.push({
50-
pathname: encodeURI(`/component_templates/${encodeURIComponent(name)}`),
51-
});
83+
redirectTo(encodeURI(`/component_templates/${encodeURIComponent(name)}`));
5284
};
5385

5486
const clearSaveError = () => {
@@ -88,7 +120,9 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
88120
<EuiSpacer size="l" />
89121

90122
<ComponentTemplateForm
91-
defaultValue={sourceComponentTemplate}
123+
defaultActiveWizardSection={defaultActiveStep}
124+
onStepChange={updateStep}
125+
defaultValue={defaultValue}
92126
onSave={onSave}
93127
isSaving={isSaving}
94128
saveError={saveError}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
8+
import { renderHook } from '@testing-library/react-hooks';
9+
10+
import { useComponentTemplatesContext } from '../../component_templates_context';
11+
12+
import { useDatastreamsRollover } from './use_datastreams_rollover';
13+
14+
jest.mock('../../component_templates_context');
15+
16+
describe('useStepFromQueryString', () => {
17+
beforeEach(() => {
18+
jest.mocked(useComponentTemplatesContext).mockReturnValue({
19+
api: {
20+
getComponentTemplateDatastreams: jest.fn(),
21+
postDataStreamMappingsFromTemplate: jest.fn(),
22+
},
23+
overlays: { openModal: jest.fn() } as any,
24+
} as any);
25+
});
26+
it('should do nothing if there no impacted data_streams', async () => {
27+
jest
28+
.mocked(useComponentTemplatesContext().api.getComponentTemplateDatastreams)
29+
.mockResolvedValue({ data: { data_streams: [] }, error: undefined });
30+
31+
const {
32+
result: {
33+
current: { showDatastreamRolloverModal },
34+
},
35+
} = renderHook(() => useDatastreamsRollover());
36+
37+
await showDatastreamRolloverModal('logs-test.data@custom');
38+
});
39+
40+
it('should try to update mappings if there is impacted data_streams', async () => {
41+
const { api, overlays } = jest.mocked(useComponentTemplatesContext());
42+
43+
api.getComponentTemplateDatastreams.mockResolvedValue({
44+
data: { data_streams: ['logs-test.data-default'] },
45+
error: undefined,
46+
});
47+
48+
api.postDataStreamMappingsFromTemplate.mockResolvedValue({
49+
error: undefined,
50+
data: { data_streams: [] },
51+
});
52+
53+
jest
54+
.mocked(useComponentTemplatesContext().overlays.openModal)
55+
.mockReturnValue({ onClose: jest.fn() } as any);
56+
57+
const {
58+
result: {
59+
current: { showDatastreamRolloverModal },
60+
},
61+
} = renderHook(() => useDatastreamsRollover());
62+
63+
await showDatastreamRolloverModal('logs-test.data@custom');
64+
65+
expect(api.postDataStreamMappingsFromTemplate).toBeCalledTimes(1);
66+
expect(overlays.openModal).not.toBeCalled();
67+
});
68+
69+
it('should show datastream rollover modal if there is an error when updating mappings', async () => {
70+
const { api, overlays } = jest.mocked(useComponentTemplatesContext());
71+
72+
api.getComponentTemplateDatastreams.mockResolvedValue({
73+
data: { data_streams: ['logs-test.data-default'] },
74+
error: undefined,
75+
});
76+
77+
api.postDataStreamMappingsFromTemplate.mockResolvedValue({
78+
error: new Error('test'),
79+
data: { data_streams: [] },
80+
});
81+
82+
jest
83+
.mocked(useComponentTemplatesContext().overlays.openModal)
84+
.mockReturnValue({ onClose: jest.fn() } as any);
85+
86+
const {
87+
result: {
88+
current: { showDatastreamRolloverModal },
89+
},
90+
} = renderHook(() => useDatastreamsRollover());
91+
92+
await showDatastreamRolloverModal('logs-test.data@custom');
93+
expect(overlays.openModal).toBeCalled();
94+
});
95+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
8+
import React, { useCallback } from 'react';
9+
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
10+
11+
import { useComponentTemplatesContext } from '../../component_templates_context';
12+
import { MappingsDatastreamRolloverModal } from './mappings_datastreams_rollover_modal';
13+
14+
export const test = {};
15+
16+
export function useDatastreamsRollover() {
17+
const { api, overlays } = useComponentTemplatesContext();
18+
19+
const showDatastreamRolloverModal = useCallback(
20+
async (componentTemplateName: string) => {
21+
const { data: dataStreamResponse } = await api.getComponentTemplateDatastreams(
22+
componentTemplateName
23+
);
24+
const dataStreams = dataStreamResponse?.data_streams ?? [];
25+
26+
const dataStreamsToRollover: string[] = [];
27+
for (const dataStream of dataStreams) {
28+
try {
29+
const { error: applyMappingError } = await api.postDataStreamMappingsFromTemplate(
30+
dataStream
31+
);
32+
if (applyMappingError) {
33+
throw applyMappingError;
34+
}
35+
} catch (err) {
36+
dataStreamsToRollover.push(dataStream);
37+
}
38+
}
39+
40+
if (dataStreamsToRollover.length) {
41+
const ref = overlays.openModal(
42+
toMountPoint(
43+
<MappingsDatastreamRolloverModal
44+
componentTemplatename={componentTemplateName}
45+
dataStreams={dataStreamsToRollover}
46+
api={api}
47+
onClose={() => {
48+
ref.close();
49+
}}
50+
/>
51+
)
52+
);
53+
54+
await ref.onClose;
55+
}
56+
},
57+
[api, overlays]
58+
);
59+
60+
return {
61+
showDatastreamRolloverModal,
62+
};
63+
}

x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx

Lines changed: 8 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
* 2.0.
66
*/
77

8-
import React, { useState, useEffect, useMemo, useCallback } from 'react';
8+
import React, { useState, useEffect, useMemo } from 'react';
99
import { RouteComponentProps } from 'react-router-dom';
1010
import { FormattedMessage } from '@kbn/i18n-react';
11-
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
1211
import { EuiPageSection, EuiPageHeader, EuiSpacer } from '@elastic/eui';
13-
import { History } from 'history';
1412

1513
import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs';
1614
import { useComponentTemplatesContext } from '../../component_templates_context';
@@ -22,47 +20,23 @@ import {
2220
Error,
2321
} from '../../shared_imports';
2422
import { ComponentTemplateForm } from '../component_template_form';
25-
import type { WizardSection } from '../component_template_form';
2623
import { useRedirectPath } from '../../../../hooks/redirect_path';
2724
import { MANAGED_BY_FLEET } from '../../constants';
2825

29-
import { MappingsDatastreamRolloverModal } from './mappings_datastreams_rollover_modal';
26+
import { useStepFromQueryString } from '../use_step_from_query_string';
27+
import { useDatastreamsRollover } from '../component_template_datastreams_rollover/use_datastreams_rollover';
3028

3129
interface MatchParams {
3230
name: string;
3331
}
3432

35-
export function useStepFromQueryString(history: History) {
36-
const activeStep = useMemo(() => {
37-
const params = new URLSearchParams(history.location.search);
38-
if (params.has('step')) {
39-
return params.get('step') as WizardSection;
40-
}
41-
}, [history.location.search]);
42-
43-
const updateStep = useCallback(
44-
(stepId: string) => {
45-
const params = new URLSearchParams(history.location.search);
46-
if (params.has('step')) {
47-
params.set('step', stepId);
48-
history.push({
49-
search: params.toString(),
50-
});
51-
}
52-
},
53-
[history]
54-
);
55-
56-
return { activeStep, updateStep };
57-
}
58-
5933
export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
6034
match: {
6135
params: { name },
6236
},
6337
history,
6438
}) => {
65-
const { api, overlays } = useComponentTemplatesContext();
39+
const { api } = useComponentTemplatesContext();
6640
const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history);
6741
const redirectTo = useRedirectPath(history);
6842

@@ -75,6 +49,8 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<
7549
const { data: dataStreamResponse } = api.useLoadComponentTemplatesDatastream(decodedName);
7650
const dataStreams = useMemo(() => dataStreamResponse?.data_streams ?? [], [dataStreamResponse]);
7751

52+
const { showDatastreamRolloverModal } = useDatastreamsRollover();
53+
7854
useEffect(() => {
7955
breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.componentTemplateEdit);
8056
}, []);
@@ -92,37 +68,8 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<
9268
return;
9369
}
9470

95-
if (updatedComponentTemplate._meta?.managed_by === MANAGED_BY_FLEET && dataStreams.length) {
96-
const dataStreamsToRollover: string[] = [];
97-
for (const dataStream of dataStreams) {
98-
try {
99-
const { error: applyMappingError } = await api.postDataStreamMappingsFromTemplate(
100-
dataStream
101-
);
102-
if (applyMappingError) {
103-
throw applyMappingError;
104-
}
105-
} catch (err) {
106-
dataStreamsToRollover.push(dataStream);
107-
}
108-
}
109-
110-
if (dataStreamsToRollover.length) {
111-
const ref = overlays.openModal(
112-
toMountPoint(
113-
<MappingsDatastreamRolloverModal
114-
componentTemplatename={updatedComponentTemplate.name}
115-
dataStreams={dataStreamsToRollover}
116-
api={api}
117-
onClose={() => {
118-
ref.close();
119-
}}
120-
/>
121-
)
122-
);
123-
124-
await ref.onClose;
125-
}
71+
if (updatedComponentTemplate._meta?.managed_by === MANAGED_BY_FLEET) {
72+
await showDatastreamRolloverModal(updatedComponentTemplate.name);
12673
}
12774
redirectTo({
12875
pathname: encodeURI(
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import { renderHook } from '@testing-library/react-hooks';
99
import { createMemoryHistory } from 'history';
10-
import { useStepFromQueryString } from './component_template_edit';
10+
import { useStepFromQueryString } from './use_step_from_query_string';
1111

1212
describe('useStepFromQueryString', () => {
1313
it('should return undefined if no step is set in the url', () => {

0 commit comments

Comments
 (0)