diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index 8bd8672b8fbb..79daba4c7386 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -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); @@ -229,6 +235,7 @@ const registerHttpRequestMockHelpers = ( setLoadIndexStatsResponse, setUpdateIndexSettingsResponse, setSimulateTemplateResponse, + setSimulateTemplateByNameResponse, setLoadComponentTemplatesResponse, setLoadNodesPluginsResponse, setLoadTelemetryResponse, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts index 615b8df18f90..ea536becfcca 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -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); diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx index ed22baae580c..fd1df7ba4469 100644 --- a/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx +++ b/x-pack/plugins/index_management/public/application/components/index_templates/simulate_template/simulate_template.tsx @@ -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) { @@ -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(); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx index 38f4a8b4f787..02df1f6e1c68 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_preview.tsx @@ -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 (
@@ -29,7 +28,7 @@ export const TabPreview = ({ templateDetails }: Props) => { - +
); }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx index d2156d1aa958..75446c0c05f2 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx @@ -171,7 +171,7 @@ export const TemplateDetailsContent = ({ [SETTINGS_TAB_ID]: , [MAPPINGS_TAB_ID]: , [ALIASES_TAB_ID]: , - [PREVIEW_TAB_ID]: , + [PREVIEW_TAB_ID]: , }; const tabContent = tabToComponentMap[activeTab]; diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 08baa4971357..9f03007014c4 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -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; diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts index 3dc6201f0831..60e2cfbf8a53 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts @@ -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; + // 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) { diff --git a/x-pack/test/api_integration/apis/management/index_management/lib/templates.api.ts b/x-pack/test/api_integration/apis/management/index_management/lib/templates.api.ts index bae578e6c049..21585d9f699a 100644 --- a/x-pack/test/api_integration/apis/management/index_management/lib/templates.api.ts +++ b/x-pack/test/api_integration/apis/management/index_management/lib/templates.api.ts @@ -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, @@ -61,5 +67,6 @@ export function templatesApi(getService: FtrProviderContext['getService']) { deleteTemplates, cleanUpTemplates, simulateTemplate, + simulateTemplateByName, }; } diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.ts b/x-pack/test/api_integration/apis/management/index_management/templates.ts index 66d6f34baa64..1fe7e022bfc9 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.ts +++ b/x-pack/test/api_integration/apis/management/index_management/templates.ts @@ -24,6 +24,7 @@ export default function ({ getService }: FtrProviderContext) { updateTemplate, cleanUpTemplates, simulateTemplate, + simulateTemplateByName, } = templatesApi(getService); describe('index templates', () => { @@ -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 }]); + }); }); }); } diff --git a/x-pack/test/functional/apps/index_management/index_template_wizard.ts b/x-pack/test/functional/apps/index_management/index_template_wizard.ts index cf6f1bf6a44a..581a0b276164 100644 --- a/x-pack/test/functional/apps/index_management/index_template_wizard.ts +++ b/x-pack/test/functional/apps/index_management/index_template_wizard.ts @@ -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');