Skip to content

Commit

Permalink
[8.x] [index management] Index templates matching `a_fake_index_…
Browse files Browse the repository at this point in the history
…pattern_that_wont_match_any_indices` preview fix (#195174) (#199983)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[index management] Index templates matching
`a_fake_index_pattern_that_wont_match_any_indices` preview fix
(#195174)](#195174)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Matthew
Kime","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-13T11:43:41Z","message":"[index
management] Index templates matching
`a_fake_index_pattern_that_wont_match_any_indices` preview fix
(#195174)\n\n## Summary\r\n\r\nPreview functionality (Index template
detail -> preview tab) on index\r\ntemplates matching
`a_fake_index_pattern_that_wont_match_any_indices`\r\n(such as `*` or
`a*`) would fail once saved.\r\n\r\nNow passing template name instead of
content for saved templates which\r\navoids annoyances with passing the
`index_pattern`.\r\n\r\nLinked issue has a good set of steps for
reproduction.\r\n\r\nCloses
https://github.com/elastic/kibana/issues/189555","sha":"a15ab4d8557c371563eab8da6d5ef8c21b6bbbcb","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Index
Management","Team:Kibana
Management","release_note:skip","v9.0.0","backport:prev-minor"],"title":"[index
management] Index templates matching
`a_fake_index_pattern_that_wont_match_any_indices` preview
fix","number":195174,"url":"https://github.com/elastic/kibana/pull/195174","mergeCommit":{"message":"[index
management] Index templates matching
`a_fake_index_pattern_that_wont_match_any_indices` preview fix
(#195174)\n\n## Summary\r\n\r\nPreview functionality (Index template
detail -> preview tab) on index\r\ntemplates matching
`a_fake_index_pattern_that_wont_match_any_indices`\r\n(such as `*` or
`a*`) would fail once saved.\r\n\r\nNow passing template name instead of
content for saved templates which\r\navoids annoyances with passing the
`index_pattern`.\r\n\r\nLinked issue has a good set of steps for
reproduction.\r\n\r\nCloses
https://github.com/elastic/kibana/issues/189555","sha":"a15ab4d8557c371563eab8da6d5ef8c21b6bbbcb"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195174","number":195174,"mergeCommit":{"message":"[index
management] Index templates matching
`a_fake_index_pattern_that_wont_match_any_indices` preview fix
(#195174)\n\n## Summary\r\n\r\nPreview functionality (Index template
detail -> preview tab) on index\r\ntemplates matching
`a_fake_index_pattern_that_wont_match_any_indices`\r\n(such as `*` or
`a*`) would fail once saved.\r\n\r\nNow passing template name instead of
content for saved templates which\r\navoids annoyances with passing the
`index_pattern`.\r\n\r\nLinked issue has a good set of steps for
reproduction.\r\n\r\nCloses
https://github.com/elastic/kibana/issues/189555","sha":"a15ab4d8557c371563eab8da6d5ef8c21b6bbbcb"}}]}]
BACKPORT-->

Co-authored-by: Matthew Kime <[email protected]>
  • Loading branch information
kibanamachine and mattkime authored Nov 13, 2024
1 parent cbf8a49 commit 176b691
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ const registerHttpRequestMockHelpers = (
const setSimulateTemplateResponse = (response?: HttpResponse, error?: ResponseError) =>
mockResponse('POST', `${API_BASE_PATH}/index_templates/simulate`, response, error);

const setSimulateTemplateByNameResponse = (
name: string,
response?: HttpResponse,
error?: ResponseError
) => mockResponse('POST', `${API_BASE_PATH}/index_templates/simulate/${name}`, response, error);

const setLoadComponentTemplatesResponse = (response?: HttpResponse, error?: ResponseError) =>
mockResponse('GET', `${API_BASE_PATH}/component_templates`, response, error);

Expand Down Expand Up @@ -229,6 +235,7 @@ const registerHttpRequestMockHelpers = (
setLoadIndexStatsResponse,
setUpdateIndexSettingsResponse,
setSimulateTemplateResponse,
setSimulateTemplateByNameResponse,
setLoadComponentTemplatesResponse,
setLoadNodesPluginsResponse,
setLoadTelemetryResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,9 @@ describe('Index Templates tab', () => {
const { find, actions, exists } = testBed;

httpRequestsMockHelpers.setLoadTemplateResponse(templates[0].name, template);
httpRequestsMockHelpers.setSimulateTemplateResponse({ simulateTemplate: 'response' });
httpRequestsMockHelpers.setSimulateTemplateByNameResponse(templates[0].name, {
simulateTemplate: 'response',
});

await actions.clickTemplateAt(0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,25 @@ export interface Filters {
}

interface Props {
template: { [key: string]: any };
template?: { [key: string]: any };
filters?: Filters;
templateName?: string;
}

export const SimulateTemplate = React.memo(({ template, filters }: Props) => {
export const SimulateTemplate = React.memo(({ template, filters, templateName }: Props) => {
const [templatePreview, setTemplatePreview] = useState('{}');

const updatePreview = useCallback(async () => {
if (!template || Object.keys(template).length === 0) {
if (!templateName && (!template || Object.keys(template).length === 0)) {
return;
}

const indexTemplate = serializeTemplate(
stripEmptyFields(template, { types: ['string'] }) as TemplateDeserialized
);
const { data, error } = await simulateIndexTemplate(indexTemplate);
const indexTemplate = templateName
? undefined
: serializeTemplate(
stripEmptyFields(template, { types: ['string'] }) as TemplateDeserialized
);
const { data, error } = await simulateIndexTemplate({ template: indexTemplate, templateName });
let filteredTemplate = data;

if (data) {
Expand Down Expand Up @@ -67,7 +70,7 @@ export const SimulateTemplate = React.memo(({ template, filters }: Props) => {
}

setTemplatePreview(JSON.stringify(filteredTemplate ?? error, null, 2));
}, [template, filters]);
}, [template, filters, templateName]);

useEffect(() => {
updatePreview();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiText, EuiSpacer } from '@elastic/eui';
import { TemplateDeserialized } from '../../../../../../../common';
import { SimulateTemplate } from '../../../../../components/index_templates';

interface Props {
templateDetails: TemplateDeserialized;
templateName: string;
}

export const TabPreview = ({ templateDetails }: Props) => {
export const TabPreview = ({ templateName }: Props) => {
return (
<div data-test-subj="previewTabContent">
<EuiText>
Expand All @@ -29,7 +28,7 @@ export const TabPreview = ({ templateDetails }: Props) => {

<EuiSpacer size="m" />

<SimulateTemplate template={templateDetails} />
<SimulateTemplate templateName={templateName} />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export const TemplateDetailsContent = ({
[SETTINGS_TAB_ID]: <TabSettings settings={settings} />,
[MAPPINGS_TAB_ID]: <TabMappings mappings={mappings} />,
[ALIASES_TAB_ID]: <TabAliases aliases={aliases} />,
[PREVIEW_TAB_ID]: <TabPreview templateDetails={templateDetails} />,
[PREVIEW_TAB_ID]: <TabPreview templateName={templateName} />,
};

const tabContent = tabToComponentMap[activeTab];
Expand Down
18 changes: 15 additions & 3 deletions x-pack/plugins/index_management/public/application/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,23 @@ export async function updateTemplate(template: TemplateDeserialized) {
return result;
}

export function simulateIndexTemplate(template: { [key: string]: any }) {
export function simulateIndexTemplate({
template,
templateName,
}: {
template?: { [key: string]: any };
templateName?: string;
}) {
const path = templateName
? `${API_BASE_PATH}/index_templates/simulate/${templateName}`
: `${API_BASE_PATH}/index_templates/simulate`;

const body = templateName ? undefined : JSON.stringify(template);

return sendRequest({
path: `${API_BASE_PATH}/index_templates/simulate`,
path,
method: 'post',
body: JSON.stringify(template),
body,
}).then((result) => {
uiMetricService.trackMetric(METRIC_TYPE.COUNT, UIM_TEMPLATE_SIMULATE);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,38 @@ const bodySchema = schema.object({}, { unknowns: 'allow' });
export function registerSimulateRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post(
{
path: addBasePath('/index_templates/simulate'),
validate: { body: bodySchema },
path: addBasePath('/index_templates/simulate/{templateName?}'),
validate: {
body: schema.nullable(bodySchema),
params: schema.object({ templateName: schema.maybe(schema.string()) }),
},
},
async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const template = request.body as TypeOf<typeof bodySchema>;
// Until ES fixes a bug on their side we need to send a fake index pattern
// that won't match any indices.
// Issue: https://github.com/elastic/elasticsearch/issues/59152
// eslint-disable-next-line @typescript-eslint/naming-convention
const index_patterns = ['a_fake_index_pattern_that_wont_match_any_indices'];
const templateName = request.params.templateName;

const params: estypes.IndicesSimulateTemplateRequest = templateName
? {
name: templateName,
body: {
index_patterns,
},
}
: {
body: {
...template,
index_patterns,
},
};

try {
const templatePreview = await client.asCurrentUser.indices.simulateTemplate({
body: {
...template,
// Until ES fixes a bug on their side we need to send a fake index pattern
// that won't match any indices.
// Issue: https://github.com/elastic/elasticsearch/issues/59152
index_patterns: ['a_fake_index_pattern_that_wont_match_any_indices'],
},
} as estypes.IndicesSimulateTemplateRequest);
const templatePreview = await client.asCurrentUser.indices.simulateTemplate(params);

return response.ok({ body: templatePreview });
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export function templatesApi(getService: FtrProviderContext['getService']) {
.set('kbn-xsrf', 'xxx')
.send(payload);

const simulateTemplateByName = (name: string) =>
supertest
.post(`${API_BASE_PATH}/index_templates/simulate/${name}`)
.set('kbn-xsrf', 'xxx')
.send();

return {
getAllTemplates,
getOneTemplate,
Expand All @@ -61,5 +67,6 @@ export function templatesApi(getService: FtrProviderContext['getService']) {
deleteTemplates,
cleanUpTemplates,
simulateTemplate,
simulateTemplateByName,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function ({ getService }: FtrProviderContext) {
updateTemplate,
cleanUpTemplates,
simulateTemplate,
simulateTemplateByName,
} = templatesApi(getService);

describe('index templates', () => {
Expand Down Expand Up @@ -452,6 +453,18 @@ export default function ({ getService }: FtrProviderContext) {
const { body } = await simulateTemplate(payload).expect(200);
expect(body.template).to.be.ok();
});

it('should simulate an index template by name', async () => {
const templateName = `template-${getRandomString()}`;
const payload = getTemplatePayload(templateName, [getRandomString()]);

await createTemplate(payload).expect(200);

await simulateTemplateByName(templateName).expect(200);

// cleanup
await deleteTemplates([{ name: templateName }]);
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,76 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
});

// https://github.com/elastic/kibana/pull/195174
it('can preview index template that matches a_fake_index_pattern_that_wont_match_any_indices', async () => {
// Click Create Template button
await testSubjects.click('createTemplateButton');
const pageTitleText = await testSubjects.getVisibleText('pageTitle');
expect(pageTitleText).to.be('Create template');

const stepTitle1 = await testSubjects.getVisibleText('stepTitle');
expect(stepTitle1).to.be('Logistics');

// Fill out required fields
await testSubjects.setValue('nameField', 'a-star');
await testSubjects.setValue('indexPatternsField', 'a*');
await testSubjects.setValue('priorityField', '1000');

// Click Next button
await pageObjects.indexManagement.clickNextButton();

// Verify empty prompt
const emptyPrompt = await testSubjects.exists('emptyPrompt');
expect(emptyPrompt).to.be(true);

// Click Next button
await pageObjects.indexManagement.clickNextButton();

// Verify step title
const stepTitle2 = await testSubjects.getVisibleText('stepTitle');
expect(stepTitle2).to.be('Index settings (optional)');

// Click Next button
await pageObjects.indexManagement.clickNextButton();

// Verify step title
const stepTitle3 = await testSubjects.getVisibleText('stepTitle');
expect(stepTitle3).to.be('Mappings (optional)');

// Click Next button
await pageObjects.indexManagement.clickNextButton();

// Verify step title
const stepTitle4 = await testSubjects.getVisibleText('stepTitle');
expect(stepTitle4).to.be('Aliases (optional)');

// Click Next button
await pageObjects.indexManagement.clickNextButton();

// Verify step title
const stepTitle = await testSubjects.getVisibleText('stepTitle');
expect(stepTitle).to.be("Review details for 'a-star'");

// Verify that summary exists
const summaryTabContent = await testSubjects.exists('summaryTabContent');
expect(summaryTabContent).to.be(true);

// Verify that index mode is set to "Standard"
expect(await testSubjects.exists('indexModeTitle')).to.be(true);
expect(await testSubjects.getVisibleText('indexModeValue')).to.be('Standard');

// Click Create template
await pageObjects.indexManagement.clickNextButton();

// Click preview tab, we know its the last one
const tabs = await testSubjects.findAll('tab');
await tabs[tabs.length - 1].click();
const templatePreview = await testSubjects.getVisibleText('simulateTemplatePreview');
expect(templatePreview).to.not.contain('error');

await testSubjects.click('closeDetailsButton');
});

describe('Mappings step', () => {
beforeEach(async () => {
await pageObjects.common.navigateToApp('indexManagement');
Expand Down

0 comments on commit 176b691

Please sign in to comment.