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 12cf7ccac6c590..20b1e0878a730c 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 @@ -103,6 +103,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setLoadComponentTemplatesResponse = (response?: HttpResponse, error?: any) => { + const status = error ? error.status || 400 : 200; + const body = error ? error.body : response; + + server.respondWith('GET', `${API_BASE_PATH}/component_templates`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + return { setLoadTemplatesResponse, setLoadIndicesResponse, @@ -114,6 +125,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setCreateTemplateResponse, setUpdateTemplateResponse, setSimulateTemplateResponse, + setLoadComponentTemplatesResponse, }; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 910d9be842da8d..e221c3d421e023 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -24,7 +24,11 @@ import { ExtensionsService } from '../../../public/services'; import { UiMetricService } from '../../../public/application/services/ui_metric'; import { setUiMetricService } from '../../../public/application/services/api'; import { setExtensionsService } from '../../../public/application/store/selectors'; -import { MappingsEditorProvider } from '../../../public/application/components'; +import { + MappingsEditorProvider, + ComponentTemplatesProvider, +} from '../../../public/application/components'; +import { componentTemplatesMockDependencies } from '../../../public/application/components/component_templates/__jest__'; import { init as initHttpRequests } from './http_requests'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); @@ -34,9 +38,11 @@ export const services = { extensionsService: new ExtensionsService(), uiMetricService: new UiMetricService('index_management'), }; + services.uiMetricService.setup({ reportUiStats() {} } as any); setExtensionsService(services.extensionsService); setUiMetricService(services.uiMetricService); + const appDependencies = { services, core: { getUrlForApp: () => {} }, @@ -66,9 +72,11 @@ export const WithAppDependencies = (Comp: any, overridingDependencies: any = {}) return ( - - - + + + + + ); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts index 3f6e5d7d4dab23..ef5cffc05d8d74 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts @@ -26,7 +26,5 @@ export const ALIASES = { }; export const MAPPINGS = { - _source: {}, - _meta: {}, properties: {}, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx index ccb729db44f9bb..8b74e9fb0cdf88 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { getTemplate } from '../../../test/fixtures'; -import { setupEnvironment, nextTick } from '../helpers'; +import { getComposableTemplate } from '../../../test/fixtures'; +import { setupEnvironment } from '../helpers'; import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, MAPPINGS } from './constants'; import { setup } from './template_clone.helpers'; @@ -41,32 +41,35 @@ jest.mock('@elastic/eui', () => { }; }); -// FLAKY: https://github.com/elastic/kibana/issues/59849 -describe.skip('', () => { +const templateToClone = getComposableTemplate({ + name: TEMPLATE_NAME, + indexPatterns: ['indexPattern1'], + template: { + mappings: MAPPINGS, + }, +}); + +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); - afterAll(() => { - server.restore(); + beforeAll(() => { + jest.useFakeTimers(); + httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); + httpRequestsMockHelpers.setLoadTemplateResponse(templateToClone); }); - const templateToClone = getTemplate({ - name: TEMPLATE_NAME, - indexPatterns: ['indexPattern1'], - template: { - mappings: MAPPINGS, - }, - isLegacy: true, + afterAll(() => { + server.restore(); + jest.useRealTimers(); }); beforeEach(async () => { - httpRequestsMockHelpers.setLoadTemplateResponse(templateToClone); - await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); }); + testBed.component.update(); }); test('should set the correct page title', () => { @@ -80,30 +83,26 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - await act(async () => { - // Complete step 1 (logistics) - // Specify index patterns, but do not change name (keep default) - await actions.completeStepOne({ - indexPatterns: DEFAULT_INDEX_PATTERNS, - }); - - // Bypass step 2 (index settings) - await actions.completeStepTwo(); - - // Bypass step 3 (mappings) - await actions.completeStepThree(); - - // Bypass step 4 (aliases) - await actions.completeStepFour(); + // Logistics + // Specify index patterns, but do not change name (keep default) + await actions.completeStepOne({ + indexPatterns: DEFAULT_INDEX_PATTERNS, }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree(); + // Mappings + await actions.completeStepFour(); + // Aliases + await actions.completeStepFive(); }); it('should send the correct payload', async () => { const { actions } = testBed; await act(async () => { - actions.clickSubmitButton(); - await nextTick(); + actions.clickNextButton(); }); const latestRequest = server.requests[server.requests.length - 1]; @@ -113,6 +112,8 @@ describe.skip('', () => { name: `${templateToClone.name}-copy`, indexPatterns: DEFAULT_INDEX_PATTERNS, }; + // @ts-expect-error + delete expected.template; // As no settings, mappings or aliases have been defined, no "template" param is sent expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index 76b6c34f999d5b..b67e503f8d3e23 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -7,12 +7,11 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { setupEnvironment, nextTick } from '../helpers'; +import { setupEnvironment } from '../helpers'; import { TEMPLATE_NAME, SETTINGS, - MAPPINGS, ALIASES, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, } from './constants'; @@ -61,21 +60,50 @@ const KEYWORD_MAPPING_FIELD = { type: 'keyword', }; -// FLAKY: https://github.com/elastic/kibana/issues/67833 -describe.skip('', () => { +const componentTemplate1 = { + name: 'test_component_template_1', + hasMappings: true, + hasAliases: false, + hasSettings: false, + usedBy: [], + isManaged: false, +}; + +const componentTemplate2 = { + name: 'test_component_template_2', + hasMappings: false, + hasAliases: false, + hasSettings: true, + usedBy: ['test_index_template_1'], + isManaged: false, +}; + +const componentTemplates = [componentTemplate1, componentTemplate2]; + +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); + beforeAll(() => { + jest.useFakeTimers(); + + httpRequestsMockHelpers.setLoadComponentTemplatesResponse(componentTemplates); + + // disable all react-beautiful-dnd development warnings + (window as any)['__react-beautiful-dnd-disable-dev-warnings'] = true; + }); + afterAll(() => { server.restore(); + jest.useRealTimers(); + (window as any)['__react-beautiful-dnd-disable-dev-warnings'] = false; }); describe('on component mount', () => { beforeEach(async () => { await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); }); }); @@ -93,9 +121,8 @@ describe.skip('', () => { await act(async () => { actions.clickNextButton(); - await nextTick(); - component.update(); }); + component.update(); expect(find('nextButton').props().disabled).toEqual(true); }); @@ -105,21 +132,131 @@ describe.skip('', () => { beforeEach(async () => { await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); }); + testBed.component.update(); }); - describe('index settings (step 2)', () => { + describe('component templates (step 2)', () => { beforeEach(async () => { const { actions } = testBed; + await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); + }); + + it('should set the correct page title', async () => { + const { exists, find } = testBed; + + expect(exists('stepComponents')).toBe(true); + expect(find('stepTitle').text()).toEqual('Component templates (optional)'); + }); + + it('should list the available component templates', () => { + const { + actions: { + componentTemplates: { getComponentTemplatesInList }, + }, + } = testBed; + const componentsFound = getComponentTemplatesInList(); + expect(componentsFound).toEqual(componentTemplates.map((c) => c.name)); + }); + + it('should allow to search for a component', async () => { + const { + component, + form: { setInputValue }, + actions: { + componentTemplates: { getComponentTemplatesInList }, + }, + } = testBed; await act(async () => { - // Complete step 1 (logistics) - await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); + setInputValue('componentTemplateSearchBox', 'template_2'); }); + component.update(); + + const componentsFound = getComponentTemplatesInList(); + expect(componentsFound).toEqual(['test_component_template_2']); }); - it('should set the correct page title', () => { + it('should allow to filter component by Index settings, mappings and aliases', async () => { + const { + find, + exists, + actions: { + componentTemplates: { showFilters, selectFilter, getComponentTemplatesInList }, + }, + } = testBed; + + showFilters(); + + expect(find('filterList.filterItem').map((wrapper) => wrapper.text())).toEqual([ + 'Index settings', + 'Mappings', + 'Aliases', + ]); + + await selectFilter('settings'); + expect(getComponentTemplatesInList()).toEqual(['test_component_template_2']); // only this one has settings + + await selectFilter('mappings'); + expect(exists('componentTemplatesList')).toBe(false); // no component has **both** settings and mappings + expect(exists('componentTemplates.emptySearchResult')).toBe(true); + expect(find('componentTemplates.emptySearchResult').text()).toContain( + 'No components match your search' + ); + + await selectFilter('settings'); // unselect settings + expect(getComponentTemplatesInList()).toEqual(['test_component_template_1']); // only this one has mappings + + await selectFilter('mappings'); // unselect mappings (back to start) + expect(getComponentTemplatesInList()).toEqual([ + 'test_component_template_1', + 'test_component_template_2', + ]); + + await selectFilter('aliases'); + expect(exists('componentTemplatesList')).toBe(false); // no component has aliases defined. + }); + + it('should allow to select and unselect a component template', async () => { + const { + find, + exists, + actions: { + componentTemplates: { + selectComponentAt, + unSelectComponentAt, + getComponentTemplatesSelected, + }, + }, + } = testBed; + + // Start with empty selection + expect(exists('componentTemplatesSelection.emptyPrompt')).toBe(true); + expect(find('componentTemplatesSelection.emptyPrompt').text()).toContain( + 'Add component template building blocks to this template.' + ); + + // Select first component in the list + await selectComponentAt(0); + expect(exists('componentTemplatesSelection.emptyPrompt')).toBe(false); + expect(getComponentTemplatesSelected()).toEqual(['test_component_template_1']); + + // Unselect the component + await unSelectComponentAt(0); + expect(exists('componentTemplatesSelection.emptyPrompt')).toBe(true); + }); + }); + + describe('index settings (step 3)', () => { + beforeEach(async () => { + const { actions } = testBed; + // Logistics + await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); + // Component templates + await actions.completeStepTwo(); + }); + + it('should set the correct page title', async () => { const { exists, find } = testBed; expect(exists('stepSettings')).toBe(true); @@ -130,24 +267,22 @@ describe.skip('', () => { const { form, actions } = testBed; await act(async () => { - actions.completeStepTwo('{ invalidJsonString '); + actions.completeStepThree('{ invalidJsonString '); }); expect(form.getErrorsMessages()).toContain('Invalid JSON format.'); }); }); - describe('mappings (step 3)', () => { + describe('mappings (step 4)', () => { beforeEach(async () => { const { actions } = testBed; - - await act(async () => { - // Complete step 1 (logistics) - await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); - - // Complete step 2 (index settings) - await actions.completeStepTwo('{}'); - }); + // Logistics + await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree('{}'); }); it('should set the correct page title', () => { @@ -160,11 +295,9 @@ describe.skip('', () => { it('should allow the user to define document fields for a mapping', async () => { const { actions, find } = testBed; - await act(async () => { - await actions.addMappingField('field_1', 'text'); - await actions.addMappingField('field_2', 'text'); - await actions.addMappingField('field_3', 'text'); - }); + await actions.mappings.addField('field_1', 'text'); + await actions.mappings.addField('field_2', 'text'); + await actions.mappings.addField('field_3', 'text'); expect(find('fieldsListItem').length).toBe(3); }); @@ -172,10 +305,8 @@ describe.skip('', () => { it('should allow the user to remove a document field from a mapping', async () => { const { actions, find } = testBed; - await act(async () => { - await actions.addMappingField('field_1', 'text'); - await actions.addMappingField('field_2', 'text'); - }); + await actions.mappings.addField('field_1', 'text'); + await actions.mappings.addField('field_2', 'text'); expect(find('fieldsListItem').length).toBe(2); @@ -187,20 +318,17 @@ describe.skip('', () => { }); }); - describe('aliases (step 4)', () => { + describe('aliases (step 5)', () => { beforeEach(async () => { const { actions } = testBed; - - await act(async () => { - // Complete step 1 (logistics) - await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); - - // Complete step 2 (index settings) - await actions.completeStepTwo('{}'); - - // Complete step 3 (mappings) - await actions.completeStepThree(); - }); + // Logistics + await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree('{}'); + // Mappings + await actions.completeStepFour(); }); it('should set the correct page title', () => { @@ -213,39 +341,35 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { actions, form } = testBed; - await act(async () => { - // Complete step 4 (aliases) with invalid json - await actions.completeStepFour('{ invalidJsonString ', false); - }); + // Complete step 5 (aliases) with invalid json + await actions.completeStepFive('{ invalidJsonString '); expect(form.getErrorsMessages()).toContain('Invalid JSON format.'); }); }); }); - describe('review (step 5)', () => { + describe('review (step 6)', () => { beforeEach(async () => { await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); - - const { actions } = testBed; - - // Complete step 1 (logistics) - await actions.completeStepOne({ - name: TEMPLATE_NAME, - indexPatterns: DEFAULT_INDEX_PATTERNS, - }); - - // Complete step 2 (index settings) - await actions.completeStepTwo(JSON.stringify(SETTINGS)); - - // Complete step 3 (mappings) - await actions.completeStepThree(); + }); + testBed.component.update(); - // Complete step 4 (aliases) - await actions.completeStepFour(JSON.stringify(ALIASES)); + const { actions } = testBed; + // Logistics + await actions.completeStepOne({ + name: TEMPLATE_NAME, + indexPatterns: DEFAULT_INDEX_PATTERNS, }); + // Component templates + await actions.completeStepTwo('test_component_template_1'); + // Index settings + await actions.completeStepThree(JSON.stringify(SETTINGS)); + // Mappings + await actions.completeStepFour(); + // Aliases + await actions.completeStepFive(JSON.stringify(ALIASES)); }); it('should set the correct step title', () => { @@ -255,26 +379,30 @@ describe.skip('', () => { }); describe('tabs', () => { - test('should have 2 tabs', () => { + test('should have 3 tabs', () => { const { find } = testBed; - expect(find('summaryTabContent').find('.euiTab').length).toBe(2); + expect(find('summaryTabContent').find('.euiTab').length).toBe(3); expect( find('summaryTabContent') .find('.euiTab') .map((t) => t.text()) - ).toEqual(['Summary', 'Request']); + ).toEqual(['Summary', 'Preview', 'Request']); }); - test('should navigate to the Request tab', async () => { + test('should navigate to the preview and request tab', async () => { const { exists, actions } = testBed; expect(exists('summaryTab')).toBe(true); expect(exists('requestTab')).toBe(false); + expect(exists('previewTab')).toBe(false); - actions.selectSummaryTab('request'); - + await actions.review.selectTab('preview'); expect(exists('summaryTab')).toBe(false); + expect(exists('previewTab')).toBe(true); + + await actions.review.selectTab('request'); + expect(exists('previewTab')).toBe(false); expect(exists('requestTab')).toBe(true); }); }); @@ -282,24 +410,23 @@ describe.skip('', () => { it('should render a warning message if a wildcard is used as an index pattern', async () => { await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); - - const { actions } = testBed; - // Complete step 1 (logistics) - await actions.completeStepOne({ - name: TEMPLATE_NAME, - indexPatterns: ['*'], // Set wildcard index pattern - }); - - // Complete step 2 (index settings) - await actions.completeStepTwo(JSON.stringify({})); - - // Complete step 3 (mappings) - await actions.completeStepThree(); + }); + testBed.component.update(); - // Complete step 4 (aliases) - await actions.completeStepFour(JSON.stringify({})); + const { actions } = testBed; + // Logistics + await actions.completeStepOne({ + name: TEMPLATE_NAME, + indexPatterns: ['*'], // Set wildcard index pattern }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree(JSON.stringify({})); + // Mappings + await actions.completeStepFour(); + // Aliases + await actions.completeStepFive(JSON.stringify({})); const { exists, find } = testBed; @@ -316,32 +443,32 @@ describe.skip('', () => { await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); - - const { actions } = testBed; - // Complete step 1 (logistics) - await actions.completeStepOne({ - name: TEMPLATE_NAME, - indexPatterns: DEFAULT_INDEX_PATTERNS, - }); - - // Complete step 2 (index settings) - await actions.completeStepTwo(JSON.stringify(SETTINGS)); - - // Complete step 3 (mappings) - await actions.completeStepThree(MAPPING_FIELDS); + }); + testBed.component.update(); - // Complete step 4 (aliases) - await actions.completeStepFour(JSON.stringify(ALIASES)); + const { actions } = testBed; + // Logistics + await actions.completeStepOne({ + name: TEMPLATE_NAME, + indexPatterns: DEFAULT_INDEX_PATTERNS, }); + // Component templates + await actions.completeStepTwo('test_component_template_1'); + // Index settings + await actions.completeStepThree(JSON.stringify(SETTINGS)); + // Mappings + await actions.completeStepFour(MAPPING_FIELDS); + // Aliases + await actions.completeStepFive(JSON.stringify(ALIASES)); }); it('should send the correct payload', async () => { - const { actions } = testBed; + const { actions, find } = testBed; + + expect(find('stepTitle').text()).toEqual(`Review details for '${TEMPLATE_NAME}'`); await act(async () => { - actions.clickSubmitButton(); - await nextTick(); + actions.clickNextButton(); }); const latestRequest = server.requests[server.requests.length - 1]; @@ -349,10 +476,10 @@ describe.skip('', () => { const expected = { name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, + composedOf: ['test_component_template_1'], template: { settings: SETTINGS, mappings: { - ...MAPPINGS, properties: { [BOOLEAN_MAPPING_FIELD.name]: { type: BOOLEAN_MAPPING_FIELD.type, @@ -370,6 +497,7 @@ describe.skip('', () => { _kbnMeta: { type: 'default', isLegacy: false, + hasDatastream: false, }, }; @@ -388,10 +516,9 @@ describe.skip('', () => { httpRequestsMockHelpers.setCreateTemplateResponse(undefined, { body: error }); await act(async () => { - actions.clickSubmitButton(); - await nextTick(); - component.update(); + actions.clickNextButton(); }); + component.update(); expect(exists('saveTemplateError')).toBe(true); expect(find('saveTemplateError').text()).toContain(error.message); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index de66013241236b..37d489b6afe72c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import * as fixtures from '../../../test/fixtures'; -import { setupEnvironment, nextTick } from '../helpers'; +import { setupEnvironment } from '../helpers'; import { TEMPLATE_NAME, SETTINGS, ALIASES, MAPPINGS as DEFAULT_MAPPING } from './constants'; import { setup } from './template_edit.helpers'; @@ -52,51 +52,51 @@ jest.mock('@elastic/eui', () => { }; }); -// FLAKY: https://github.com/elastic/kibana/issues/65567 -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); + beforeAll(() => { + jest.useFakeTimers(); + httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); + }); + afterAll(() => { server.restore(); + jest.useRealTimers(); }); describe('without mappings', () => { const templateToEdit = fixtures.getTemplate({ name: 'index_template_without_mappings', indexPatterns: ['indexPattern1'], - isLegacy: true, }); - beforeEach(async () => { + beforeAll(() => { httpRequestsMockHelpers.setLoadTemplateResponse(templateToEdit); + }); - testBed = await setup(); - + beforeEach(async () => { await act(async () => { - await nextTick(); - testBed.component.update(); + testBed = await setup(); }); + + testBed.component.update(); }); it('allows you to add mappings', async () => { const { actions, find } = testBed; - - await act(async () => { - // Complete step 1 (logistics) - await actions.completeStepOne(); - - // Step 2 (index settings) - await actions.completeStepTwo(); - - // Step 3 (mappings) - await act(async () => { - await actions.addMappingField('field_1', 'text'); - }); - - expect(find('fieldsListItem').length).toBe(1); - }); + // Logistics + await actions.completeStepOne(); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree(); + // Mappings + await actions.mappings.addField('field_1', 'text'); + + expect(find('fieldsListItem').length).toBe(1); }); }); @@ -107,16 +107,17 @@ describe.skip('', () => { template: { mappings: MAPPING, }, - isLegacy: true, }); - beforeEach(async () => { + beforeAll(() => { httpRequestsMockHelpers.setLoadTemplateResponse(templateToEdit); + }); + beforeEach(async () => { await act(async () => { testBed = await setup(); - await testBed.waitFor('templateForm'); }); + testBed.component.update(); }); test('should set the correct page title', () => { @@ -138,64 +139,64 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - await act(async () => { - // Complete step 1 (logistics) - await actions.completeStepOne({ - indexPatterns: UPDATED_INDEX_PATTERN, - }); - - // Step 2 (index settings) - await actions.completeStepTwo(JSON.stringify(SETTINGS)); + // Logistics + await actions.completeStepOne({ + indexPatterns: UPDATED_INDEX_PATTERN, + priority: 3, }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree(JSON.stringify(SETTINGS)); }); it('should send the correct payload with changed values', async () => { - const { actions, component, find, form } = testBed; + const { actions, component, exists, form } = testBed; + // Make some changes to the mappings await act(async () => { - // Make some changes to the mappings (step 3) - actions.clickEditButtonAtField(0); // Select the first field to edit - await nextTick(); - component.update(); }); + component.update(); - // verify edit field flyout - expect(find('mappingsEditorFieldEdit').length).toEqual(1); + // Verify that the edit field flyout is opened + expect(exists('mappingsEditorFieldEdit')).toBe(true); + // Change the field name await act(async () => { - // change the field name form.setInputValue('nameParameterInput', UPDATED_MAPPING_TEXT_FIELD_NAME); + }); - // Save changes + // Save changes on the field + await act(async () => { actions.clickEditFieldUpdateButton(); - await nextTick(); - component.update(); + }); + component.update(); - // Proceed to the next step + // Proceed to the next step + await act(async () => { actions.clickNextButton(); - await nextTick(50); - component.update(); + }); + component.update(); - // Step 4 (aliases) - await actions.completeStepFour(JSON.stringify(ALIASES)); + // Aliases + await actions.completeStepFive(JSON.stringify(ALIASES)); - // Submit the form - actions.clickSubmitButton(); - await nextTick(); + // Submit the form + await act(async () => { + actions.clickNextButton(); }); const latestRequest = server.requests[server.requests.length - 1]; - const { version, order } = templateToEdit; + const { version } = templateToEdit; const expected = { name: TEMPLATE_NAME, version, - order, + priority: 3, indexPatterns: UPDATED_INDEX_PATTERN, template: { mappings: { - ...MAPPING, properties: { [UPDATED_MAPPING_TEXT_FIELD_NAME]: { type: 'text', @@ -215,6 +216,7 @@ describe.skip('', () => { _kbnMeta: { type: 'default', isLegacy: templateToEdit._kbnMeta.isLegacy, + hasDatastream: false, }, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index fdf837a914cf1f..025410129a0021 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { act } from 'react-dom/test-utils'; import { TestBed, SetupFunc, UnwrapPromise } from '../../../../../test_utils'; import { TemplateDeserialized } from '../../../common'; -import { nextTick } from '../helpers'; interface MappingField { name: string; @@ -30,10 +30,6 @@ export const formSetup = async (initTestBed: SetupFunc) => { testBed.find('backButton').simulate('click'); }; - const clickSubmitButton = () => { - testBed.find('submitButton').simulate('click'); - }; - const clickEditButtonAtField = (index: number) => { testBed.find('editFieldButton').at(index).simulate('click'); }; @@ -52,16 +48,100 @@ export const formSetup = async (initTestBed: SetupFunc) => { testBed.find('createFieldForm.cancelButton').simulate('click'); }; + // Step component templates actions + const componentTemplates = { + getComponentTemplatesInList() { + const { find } = testBed; + return find('componentTemplatesList.item.name').map((wrapper) => wrapper.text()); + }, + getComponentTemplatesSelected() { + const { find } = testBed; + return find('componentTemplatesSelection.item.name').map((wrapper) => wrapper.text()); + }, + showFilters() { + const { find, component } = testBed; + act(() => { + find('componentTemplates.filterButton').simulate('click'); + }); + component.update(); + }, + async selectFilter(filter: 'settings' | 'mappings' | 'aliases') { + const { find, component } = testBed; + const filters = ['settings', 'mappings', 'aliases']; + const index = filters.indexOf(filter); + + await act(async () => { + find('filterList.filterItem').at(index).simulate('click'); + }); + component.update(); + }, + async selectComponentAt(index: number) { + const { find, component } = testBed; + + await act(async () => { + find('componentTemplatesList.item.action-plusInCircle').at(index).simulate('click'); + }); + component.update(); + }, + async unSelectComponentAt(index: number) { + const { find, component } = testBed; + + await act(async () => { + find('componentTemplatesSelection.item.action-minusInCircle').at(index).simulate('click'); + }); + component.update(); + }, + }; + + // Step Mappings actions + const mappings = { + async addField(name: string, type: string) { + const { find, form, component } = testBed; + + await act(async () => { + form.setInputValue('nameParameterInput', name); + find('createFieldForm.mockComboBox').simulate('change', [ + { + label: type, + value: type, + }, + ]); + }); + + await act(async () => { + find('createFieldForm.addButton').simulate('click'); + }); + + component.update(); + }, + }; + + // Step Review actions + const review = { + async selectTab(tab: 'summary' | 'preview' | 'request') { + const tabs = ['summary', 'preview', 'request']; + + await act(async () => { + testBed.find('summaryTabContent').find('.euiTab').at(tabs.indexOf(tab)).simulate('click'); + }); + + testBed.component.update(); + }, + }; + const completeStepOne = async ({ name, indexPatterns, order, + priority, version, }: Partial = {}) => { - const { form, find, waitFor } = testBed; + const { component, form, find } = testBed; if (name) { - form.setInputValue('nameField.input', name); + act(() => { + form.setInputValue('nameField.input', name); + }); } if (indexPatterns) { @@ -70,94 +150,104 @@ export const formSetup = async (initTestBed: SetupFunc) => { value: pattern, })); - find('mockComboBox').simulate('change', indexPatternsFormatted); // Using mocked EuiComboBox - await nextTick(); + act(() => { + find('mockComboBox').simulate('change', indexPatternsFormatted); // Using mocked EuiComboBox + }); } - if (order) { - form.setInputValue('orderField.input', JSON.stringify(order)); - } + await act(async () => { + if (order) { + form.setInputValue('orderField.input', JSON.stringify(order)); + } - if (version) { - form.setInputValue('versionField.input', JSON.stringify(version)); - } + if (priority) { + form.setInputValue('priorityField.input', JSON.stringify(priority)); + } - clickNextButton(); - await waitFor('stepSettings'); + if (version) { + form.setInputValue('versionField.input', JSON.stringify(version)); + } + + clickNextButton(); + }); + + component.update(); }; - const completeStepTwo = async (settings?: string) => { - const { find, component, waitFor } = testBed; + const completeStepTwo = async (componentName?: string) => { + const { find, component } = testBed; + + if (componentName) { + // Find the index of the template in the list + const allComponents = find('componentTemplatesList.item.name').map((wrapper) => + wrapper.text() + ); + const index = allComponents.indexOf(componentName); + if (index < 0) { + throw new Error( + `Could not find component "${componentName}" in the list ${JSON.stringify(allComponents)}` + ); + } - if (settings) { - find('mockCodeEditor').simulate('change', { - jsonString: settings, - }); // Using mocked EuiCodeEditor - await nextTick(); - component.update(); + await componentTemplates.selectComponentAt(index); } - clickNextButton(); - await waitFor('stepMappings'); + await act(async () => { + clickNextButton(); + }); + + component.update(); + }; + + const completeStepThree = async (settings?: string) => { + const { find, component } = testBed; + + await act(async () => { + if (settings) { + find('mockCodeEditor').simulate('change', { + jsonString: settings, + }); // Using mocked EuiCodeEditor + } + + clickNextButton(); + }); + + component.update(); }; - const completeStepThree = async (mappingFields?: MappingField[]) => { - const { waitFor } = testBed; + const completeStepFour = async (mappingFields?: MappingField[]) => { + const { component } = testBed; if (mappingFields) { for (const field of mappingFields) { const { name, type } = field; - await addMappingField(name, type); + await mappings.addField(name, type); } } - clickNextButton(); - await waitFor('stepAliases'); + await act(async () => { + clickNextButton(); + }); + + component.update(); }; - const completeStepFour = async (aliases?: string, waitForNextStep = true) => { - const { find, component, waitFor } = testBed; + const completeStepFive = async (aliases?: string) => { + const { find, component } = testBed; if (aliases) { - find('mockCodeEditor').simulate('change', { - jsonString: aliases, - }); // Using mocked EuiCodeEditor - await nextTick(); + await act(async () => { + find('mockCodeEditor').simulate('change', { + jsonString: aliases, + }); // Using mocked EuiCodeEditor + }); component.update(); } - clickNextButton(); - - if (waitForNextStep) { - await waitFor('summaryTab'); - } else { - component.update(); - } - }; - - const selectSummaryTab = (tab: 'summary' | 'request') => { - const tabs = ['summary', 'request']; - - testBed.find('summaryTabContent').find('.euiTab').at(tabs.indexOf(tab)).simulate('click'); - }; - - const addMappingField = async (name: string, type: string) => { - const { find, form, component } = testBed; - - form.setInputValue('nameParameterInput', name); - find('createFieldForm.mockComboBox').simulate('change', [ - { - label: type, - value: type, - }, - ]); - - await nextTick(50); - component.update(); - - find('createFieldForm.addButton').simulate('click'); + await act(async () => { + clickNextButton(); + }); - await nextTick(); component.update(); }; @@ -166,7 +256,6 @@ export const formSetup = async (initTestBed: SetupFunc) => { actions: { clickNextButton, clickBackButton, - clickSubmitButton, clickEditButtonAtField, clickEditFieldUpdateButton, deleteMappingsFieldAt, @@ -175,8 +264,10 @@ export const formSetup = async (initTestBed: SetupFunc) => { completeStepTwo, completeStepThree, completeStepFour, - selectSummaryTab, - addMappingField, + completeStepFive, + componentTemplates, + mappings, + review, }, }; }; @@ -187,6 +278,17 @@ export type TestSubjects = | 'backButton' | 'codeEditorContainer' | 'confirmModalConfirmButton' + | 'componentTemplates.filterButton' + | 'componentTemplates.emptySearchResult' + | 'filterList.filterItem' + | 'componentTemplatesList' + | 'componentTemplatesList.item.name' + | 'componentTemplatesList.item.action-plusInCircle' + | 'componentTemplatesSelection' + | 'componentTemplatesSelection.item.name' + | 'componentTemplatesSelection.item.action-minusInCircle' + | 'componentTemplatesSelection.emptyPrompt' + | 'componentTemplateSearchBox' | 'createFieldForm.addPropertyButton' | 'createFieldForm.addButton' | 'createFieldForm.addFieldButton' @@ -209,12 +311,15 @@ export type TestSubjects = | 'nextButton' | 'orderField' | 'orderField.input' + | 'priorityField.input' | 'pageTitle' + | 'previewTab' | 'removeFieldButton' | 'requestTab' | 'saveTemplateError' | 'settingsEditor' | 'systemTemplateEditCallout' + | 'stepComponents' | 'stepAliases' | 'stepMappings' | 'stepSettings' diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts index 93eb65aac07619..4b92235993e26d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts @@ -9,7 +9,7 @@ import { setup as componentTemplateDetailsSetup } from './component_template_det export { nextTick, getRandomString, findTestSubject } from '../../../../../../../../../test_utils'; -export { setupEnvironment } from './setup_environment'; +export { setupEnvironment, appDependencies } from './setup_environment'; export const pageHelpers = { componentTemplateList: { setup: componentTemplatesListSetup }, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx index 79e213229fc516..ac748e1b7dc2c9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx @@ -24,7 +24,7 @@ import { API_BASE_PATH } from './constants'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); const { GlobalFlyoutProvider } = GlobalFlyout; -const appDependencies = { +export const appDependencies = { httpClient: (mockHttpClient as unknown) as HttpSetup, apiBasePath: API_BASE_PATH, trackMetric: () => {}, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/index.ts new file mode 100644 index 00000000000000..ebd2cd93925688 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { appDependencies as componentTemplatesMockDependencies } from './client_integration/helpers'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.tsx index b07279c57d2bec..3c10ef2cc7edce 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.tsx @@ -145,12 +145,13 @@ export const ComponentTemplates = ({ isLoading, components, listItemProps }: Pro /> } + data-test-subj="emptySearchResult" /> ); }; return ( -
+
@@ -162,6 +163,7 @@ export const ComponentTemplates = ({ isLoading, components, listItemProps }: Pro }} aria-label={i18nTexts.searchBoxPlaceholder} className="componentTemplates__searchBox" + data-test-subj="componentTemplateSearchBox" /> diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list.tsx index 0c64c38c8963f7..519f2482c93238 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list.tsx @@ -19,10 +19,10 @@ interface Props { export const ComponentTemplatesList = ({ components, listItemProps }: Props) => { return ( - <> +
{components.map((component) => ( ))} - +
); }; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.tsx index ad75c8dcbcc543..40bb062f5ddf3c 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.tsx @@ -48,6 +48,7 @@ export const ComponentTemplatesListItem = ({ className={classNames('componentTemplatesListItem', { 'componentTemplatesListItem--selected': isSelectedValue, })} + data-test-subj="item" > @@ -59,7 +60,7 @@ export const ComponentTemplatesListItem = ({
)} - + {/* {component.name} */} onViewDetail(component)}>{component.name} @@ -83,7 +84,7 @@ export const ComponentTemplatesListItem = ({ action.handler(component)} - data-test-subj="addPropertyButton" + data-test-subj={`action-${action.icon}`} aria-label={action.label} /> diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx index ff871b8b792479..f2fd5048931de9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx @@ -160,6 +160,7 @@ export const ComponentTemplatesSelector = ({ // eslint-disable-next-line @typescript-eslint/naming-convention 'componentTemplatesSelector__selection--is-empty': !hasSelection, })} + data-test-subj="componentTemplatesSelection" > {hasSelection ? ( <> @@ -200,7 +201,7 @@ export const ComponentTemplatesSelector = ({
) : ( - +

0} numActiveFilters={activeFilters.length} - data-test-subj="viewButton" + data-test-subj="filterButton" > { - let testBed: MappingsEditorTestBed; - /** * Variable to store the mappings data forwarded to the consumer component */ let data: any; + let onChangeHandler: jest.Mock = jest.fn(); + let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); + let testBed: MappingsEditorTestBed; beforeAll(() => { jest.useFakeTimers(); @@ -34,6 +34,11 @@ describe('Mappings editor: shape datatype', () => { jest.useRealTimers(); }); + beforeEach(() => { + onChangeHandler = jest.fn(); + getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); + }); + test('initial view and default parameters values', async () => { const defaultMappings = { properties: { @@ -45,7 +50,10 @@ describe('Mappings editor: shape datatype', () => { const updatedMappings = { ...defaultMappings }; - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); const { component, @@ -53,7 +61,7 @@ describe('Mappings editor: shape datatype', () => { } = testBed; // Open the flyout to edit the field - startEditField('myField'); + await startEditField('myField'); // Save the field and close the flyout await updateFieldAndCloseFlyout(); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 66989baa2dc67c..1832bedee0143c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -9,8 +9,6 @@ import { componentHelpers, MappingsEditorTestBed } from '../helpers'; import { getFieldConfig } from '../../../lib'; const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; -const onChangeHandler = jest.fn(); -const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); // Parameters automatically added to the text datatype when saved (with the default values) export const defaultTextParameters = { @@ -24,14 +22,14 @@ export const defaultTextParameters = { store: false, }; -// FLAKY: https://github.com/elastic/kibana/issues/66669 -describe.skip('Mappings editor: text datatype', () => { - let testBed: MappingsEditorTestBed; - +describe('Mappings editor: text datatype', () => { /** * Variable to store the mappings data forwarded to the consumer component */ let data: any; + let onChangeHandler: jest.Mock = jest.fn(); + let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); + let testBed: MappingsEditorTestBed; beforeAll(() => { jest.useFakeTimers(); @@ -41,8 +39,9 @@ describe.skip('Mappings editor: text datatype', () => { jest.useRealTimers(); }); - afterEach(() => { - onChangeHandler.mockReset(); + beforeEach(() => { + onChangeHandler = jest.fn(); + getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); }); test('initial view and default parameters values', async () => { @@ -56,7 +55,10 @@ describe.skip('Mappings editor: text datatype', () => { const updatedMappings = { ...defaultMappings }; - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); const { component, @@ -64,7 +66,7 @@ describe.skip('Mappings editor: text datatype', () => { } = testBed; // Open the flyout to edit the field - startEditField('myField'); + await startEditField('myField'); // It should have searchable ("index" param) active by default const indexFieldConfig = getFieldConfig('index'); @@ -80,7 +82,7 @@ describe.skip('Mappings editor: text datatype', () => { ({ data } = await getMappingsEditorData(component)); expect(data).toEqual(updatedMappings); - }, 10000); + }); test('analyzer parameter: default values', async () => { const defaultMappings = { @@ -96,7 +98,10 @@ describe.skip('Mappings editor: text datatype', () => { }, }; - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); const { component, @@ -113,8 +118,8 @@ describe.skip('Mappings editor: text datatype', () => { const fieldToEdit = 'myField'; // Start edit and immediately save to have all the default values - startEditField(fieldToEdit); - showAdvancedSettings(); + await startEditField(fieldToEdit); + await showAdvancedSettings(); await updateFieldAndCloseFlyout(); expect(exists('mappingsEditorFieldEdit')).toBe(false); @@ -133,8 +138,8 @@ describe.skip('Mappings editor: text datatype', () => { expect(data).toEqual(updatedMappings); // Re-open the edit panel - startEditField(fieldToEdit); - showAdvancedSettings(); + await startEditField(fieldToEdit); + await showAdvancedSettings(); // When no analyzer is defined, defaults to "Index default" let indexAnalyzerValue = find('indexAnalyzer.select').props().value; @@ -158,9 +163,8 @@ describe.skip('Mappings editor: text datatype', () => { expect(exists('searchAnalyzer')).toBe(false); // Uncheck the "Use same analyzer for search" checkbox and make sure the dedicated select appears - selectCheckBox('useSameAnalyzerForSearchCheckBox.input', false); - act(() => { - jest.advanceTimersByTime(1000); + await act(async () => { + selectCheckBox('useSameAnalyzerForSearchCheckBox.input', false); }); component.update(); @@ -169,7 +173,6 @@ describe.skip('Mappings editor: text datatype', () => { let searchAnalyzerValue = find('searchAnalyzer.select').props().value; expect(searchAnalyzerValue).toEqual('index_default'); - // Change the value of the 3 analyzers await act(async () => { // Change the value of the 3 analyzers setSelectValue('indexAnalyzer.select', 'standard', false); @@ -195,8 +198,8 @@ describe.skip('Mappings editor: text datatype', () => { expect(data).toEqual(updatedMappings); // Re-open the flyout and make sure the select have the correct updated value - startEditField('myField'); - showAdvancedSettings(); + await startEditField('myField'); + await showAdvancedSettings(); isUseSameAnalyzerForSearchChecked = getCheckboxValue('useSameAnalyzerForSearchCheckBox.input'); expect(isUseSameAnalyzerForSearchChecked).toBe(false); @@ -208,7 +211,7 @@ describe.skip('Mappings editor: text datatype', () => { expect(indexAnalyzerValue).toBe('standard'); expect(searchAnalyzerValue).toBe('simple'); expect(searchQuoteAnalyzerValue).toBe('whitespace'); - }, 50000); + }); test('analyzer parameter: custom analyzer (external plugin)', async () => { const defaultMappings = { @@ -234,7 +237,10 @@ describe.skip('Mappings editor: text datatype', () => { }, }; - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); const { find, @@ -245,8 +251,8 @@ describe.skip('Mappings editor: text datatype', () => { } = testBed; const fieldToEdit = 'myField'; - startEditField(fieldToEdit); - showAdvancedSettings(); + await startEditField(fieldToEdit); + await showAdvancedSettings(); expect(exists('indexAnalyzer-custom')).toBe(true); expect(exists('searchAnalyzer-custom')).toBe(true); @@ -301,7 +307,7 @@ describe.skip('Mappings editor: text datatype', () => { }; expect(data).toEqual(updatedMappings); - }, 100000); + }); test('analyzer parameter: custom analyzer (from index settings)', async () => { const indexSettings = { @@ -320,8 +326,6 @@ describe.skip('Mappings editor: text datatype', () => { const customAnalyzers = Object.keys(indexSettings.analysis.analyzer); const defaultMappings = { - _meta: {}, - _source: {}, properties: { myField: { type: 'text', @@ -340,11 +344,14 @@ describe.skip('Mappings editor: text datatype', () => { }, }; - testBed = setup({ - value: defaultMappings, - onChange: onChangeHandler, - indexSettings, + await act(async () => { + testBed = setup({ + value: defaultMappings, + onChange: onChangeHandler, + indexSettings, + }); }); + testBed.component.update(); const { component, @@ -354,8 +361,8 @@ describe.skip('Mappings editor: text datatype', () => { } = testBed; const fieldToEdit = 'myField'; - startEditField(fieldToEdit); - showAdvancedSettings(); + await startEditField(fieldToEdit); + await showAdvancedSettings(); // It should have 2 selects const indexAnalyzerSelects = find('indexAnalyzer.select'); @@ -395,5 +402,5 @@ describe.skip('Mappings editor: text datatype', () => { }; expect(data).toEqual(updatedMappings); - }, 50000); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx index c146c7704911fa..96348178351017 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -8,10 +8,14 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed } from './helpers'; import { defaultTextParameters, defaultShapeParameters } from './datatypes'; const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; -const onChangeHandler = jest.fn(); -const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); describe('Mappings editor: edit field', () => { + /** + * Variable to store the mappings data forwarded to the consumer component + */ + let data: any; + let onChangeHandler: jest.Mock = jest.fn(); + let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); let testBed: MappingsEditorTestBed; beforeAll(() => { @@ -22,8 +26,9 @@ describe('Mappings editor: edit field', () => { jest.useRealTimers(); }); - afterEach(() => { - onChangeHandler.mockReset(); + beforeEach(() => { + onChangeHandler = jest.fn(); + getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); }); test('should open a flyout with the correct field to edit', async () => { @@ -43,7 +48,11 @@ describe('Mappings editor: edit field', () => { }, }; - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); + await testBed.actions.expandAllFieldsAndReturnMetadata(); const { @@ -51,7 +60,7 @@ describe('Mappings editor: edit field', () => { actions: { startEditField }, } = testBed; // Open the flyout to edit the field - startEditField('user.address.street'); + await startEditField('user.address.street'); // It should have the correct title expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field 'street'`); @@ -72,7 +81,10 @@ describe('Mappings editor: edit field', () => { }, }; - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); const { find, @@ -83,19 +95,18 @@ describe('Mappings editor: edit field', () => { expect(exists('userNameField' as any)).toBe(true); // Open the flyout, change the field type and save it - startEditField('userName'); + await startEditField('userName'); // Change the field type - find('mappingsEditorFieldEdit.fieldType').simulate('change', [ - { label: 'Shape', value: defaultShapeParameters.type }, - ]); - act(() => { - jest.advanceTimersByTime(1000); + await act(async () => { + find('mappingsEditorFieldEdit.fieldType').simulate('change', [ + { label: 'Shape', value: defaultShapeParameters.type }, + ]); }); await updateFieldAndCloseFlyout(); - const { data } = await getMappingsEditorData(component); + ({ data } = await getMappingsEditorData(component)); const updatedMappings = { ...defaultMappings, @@ -107,5 +118,5 @@ describe('Mappings editor: edit field', () => { }; expect(data).toEqual(updatedMappings); - }, 50000); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index a6558b28a12734..2a4af89c46559f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -149,22 +149,28 @@ const createActions = (testBed: TestBed) => { return { field: find(testSubject as TestSubjects), testSubject }; }; - const addField = (name: string, type: string) => { - form.setInputValue('nameParameterInput', name); - find('createFieldForm.fieldType').simulate('change', [ - { - label: type, - value: type, - }, - ]); - find('createFieldForm.addButton').simulate('click'); + const addField = async (name: string, type: string) => { + await act(async () => { + form.setInputValue('nameParameterInput', name); + find('createFieldForm.fieldType').simulate('change', [ + { + label: type, + value: type, + }, + ]); + }); + + await act(async () => { + find('createFieldForm.addButton').simulate('click'); + }); + + component.update(); }; - const startEditField = (path: string) => { + const startEditField = async (path: string) => { const { testSubject } = getFieldAt(path); - find(`${testSubject}.editFieldButton` as TestSubjects).simulate('click'); - act(() => { - jest.advanceTimersByTime(1000); + await act(async () => { + find(`${testSubject}.editFieldButton` as TestSubjects).simulate('click'); }); component.update(); }; @@ -174,34 +180,33 @@ const createActions = (testBed: TestBed) => { find('mappingsEditorFieldEdit.editFieldUpdateButton').simulate('click'); }); component.update(); - - act(() => { - jest.advanceTimersByTime(1000); - }); }; - const showAdvancedSettings = () => { + const showAdvancedSettings = async () => { if (find('mappingsEditorFieldEdit.advancedSettings').props().style.display === 'block') { // Already opened, nothing else to do return; } - find('mappingsEditorFieldEdit.toggleAdvancedSetting').simulate('click'); - - act(() => { - jest.advanceTimersByTime(1000); + await act(async () => { + find('mappingsEditorFieldEdit.toggleAdvancedSetting').simulate('click'); }); + component.update(); }; - const selectTab = (tab: 'fields' | 'templates' | 'advanced') => { + const selectTab = async (tab: 'fields' | 'templates' | 'advanced') => { const index = ['fields', 'templates', 'advanced'].indexOf(tab); const tabElement = find('formTab').at(index); if (tabElement.length === 0) { throw new Error(`Tab not found: "${tab}"`); } - tabElement.simulate('click'); + + await act(async () => { + tabElement.simulate('click'); + }); + component.update(); }; const updateJsonEditor = (testSubject: TestSubjects, value: object) => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 0743211a2b7bff..68933ddc9a935d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -8,15 +8,15 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed } from './helpers'; const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; -const onChangeHandler = jest.fn(); -const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); -// FLAKY: https://github.com/elastic/kibana/issues/66457 -describe.skip('Mappings editor: core', () => { +describe('Mappings editor: core', () => { /** * Variable to store the mappings data forwarded to the consumer component */ let data: any; + let onChangeHandler: jest.Mock = jest.fn(); + let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); + let testBed: MappingsEditorTestBed; beforeAll(() => { jest.useFakeTimers(); @@ -26,8 +26,9 @@ describe.skip('Mappings editor: core', () => { jest.useRealTimers(); }); - afterEach(() => { - onChangeHandler.mockReset(); + beforeEach(() => { + onChangeHandler = jest.fn(); + getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); }); test('default behaviour', async () => { @@ -42,11 +43,14 @@ describe.skip('Mappings editor: core', () => { }, }; - const { component } = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + + const { component } = testBed; + component.update(); const expectedMappings = { - _meta: {}, // Was not defined so an empty object is returned - _source: {}, // Was not defined so an empty object is returned ...defaultMappings, properties: { user: { @@ -78,8 +82,13 @@ describe.skip('Mappings editor: core', () => { }, }, }; - const testBed = setup({ onChange: onChangeHandler, value }); - const { exists } = testBed; + + await act(async () => { + testBed = setup({ onChange: onChangeHandler, value }); + }); + + const { component, exists } = testBed; + component.update(); expect(exists('mappingsEditor')).toBe(true); expect(exists('mappingTypesDetectedCallout')).toBe(true); @@ -94,8 +103,12 @@ describe.skip('Mappings editor: core', () => { }, }, }; - const testBed = setup({ onChange: onChangeHandler, value }); - const { exists } = testBed; + await act(async () => { + testBed = setup({ onChange: onChangeHandler, value }); + }); + + const { component, exists } = testBed; + component.update(); expect(exists('mappingsEditor')).toBe(true); expect(exists('mappingTypesDetectedCallout')).toBe(false); @@ -108,10 +121,12 @@ describe.skip('Mappings editor: core', () => { properties: {}, dynamic_templates: [{ before: 'foo' }], }; - let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); }); test('should keep the changes when switching tabs', async () => { @@ -129,10 +144,7 @@ describe.skip('Mappings editor: core', () => { expect(find('fieldsListItem').length).toEqual(0); // Check that we start with an empty list const newField = { name: 'John', type: 'text' }; - await act(async () => { - addField(newField.name, newField.type); - }); - component.update(); + await addField(newField.name, newField.type); expect(find('fieldsListItem').length).toEqual(1); @@ -142,10 +154,7 @@ describe.skip('Mappings editor: core', () => { // ------------------------------------- // Navigate to dynamic templates tab // ------------------------------------- - await act(async () => { - selectTab('templates'); - }); - component.update(); + await selectTab('templates'); let templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); expect(templatesValue).toEqual(defaultMappings.dynamic_templates); @@ -163,10 +172,7 @@ describe.skip('Mappings editor: core', () => { // ------------------------------------------------------ // Switch to advanced settings tab and make some changes // ------------------------------------------------------ - await act(async () => { - selectTab('advanced'); - }); - component.update(); + await selectTab('advanced'); let isDynamicMappingsEnabled = getToggleValue( 'advancedConfiguration.dynamicMappingsToggle.input' @@ -194,10 +200,7 @@ describe.skip('Mappings editor: core', () => { // ---------------------------------------------------------------------------- // Go back to dynamic templates tab and make sure our changes are still there // ---------------------------------------------------------------------------- - await act(async () => { - selectTab('templates'); - }); - component.update(); + await selectTab('templates'); templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); expect(templatesValue).toEqual(updatedValueTemplates); @@ -205,18 +208,13 @@ describe.skip('Mappings editor: core', () => { // ----------------------------------------------------------- // Go back to fields and make sure our created field is there // ----------------------------------------------------------- - await act(async () => { - selectTab('fields'); - }); - component.update(); + await selectTab('fields'); + field = find('fieldsListItem').at(0); expect(find('fieldName', field).text()).toEqual(newField.name); // Go back to advanced settings tab make sure dynamic mappings is disabled - await act(async () => { - selectTab('advanced'); - }); - component.update(); + await selectTab('advanced'); isDynamicMappingsEnabled = getToggleValue( 'advancedConfiguration.dynamicMappingsToggle.input' @@ -231,46 +229,47 @@ describe.skip('Mappings editor: core', () => { /** * Note: the "indexSettings" prop will be tested along with the "analyzer" parameter on a text datatype field, * as it is the only place where it is consumed by the mappings editor. - * - * The test that covers it is text_datatype.test.tsx: "analyzer parameter: custom analyzer (from index settings)" + * The test that covers it is in the "text_datatype.test.tsx": "analyzer parameter: custom analyzer (from index settings)" */ - const defaultMappings: any = { - dynamic: true, - numeric_detection: false, - date_detection: true, - properties: { - title: { type: 'text' }, - address: { - type: 'object', - properties: { - street: { type: 'text' }, - city: { type: 'text' }, + let defaultMappings: any; + + beforeEach(async () => { + defaultMappings = { + dynamic: true, + numeric_detection: false, + date_detection: true, + properties: { + title: { type: 'text' }, + address: { + type: 'object', + properties: { + street: { type: 'text' }, + city: { type: 'text' }, + }, }, }, - }, - dynamic_templates: [{ initial: 'value' }], - _source: { - enabled: true, - includes: ['field1', 'field2'], - excludes: ['field3'], - }, - _meta: { - some: 'metaData', - }, - _routing: { - required: false, - }, - }; - - let testBed: MappingsEditorTestBed; + dynamic_templates: [{ initial: 'value' }], + _source: { + enabled: true, + includes: ['field1', 'field2'], + excludes: ['field3'], + }, + _meta: { + some: 'metaData', + }, + _routing: { + required: false, + }, + }; - beforeEach(async () => { - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); }); test('props.value => should prepopulate the editor data', async () => { const { - component, actions: { selectTab, getJsonEditorValue, getComboBoxValue, getToggleValue }, find, } = testBed; @@ -285,10 +284,7 @@ describe.skip('Mappings editor: core', () => { /** * Dynamic templates */ - await act(async () => { - selectTab('templates'); - }); - component.update(); + await selectTab('templates'); // Test that dynamic templates JSON is rendered in the templates editor const templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); @@ -297,10 +293,7 @@ describe.skip('Mappings editor: core', () => { /** * Advanced settings */ - await act(async () => { - selectTab('advanced'); - }); - component.update(); + await selectTab('advanced'); const isDynamicMappingsEnabled = getToggleValue( 'advancedConfiguration.dynamicMappingsToggle.input' @@ -339,7 +332,14 @@ describe.skip('Mappings editor: core', () => { /** * Mapped fields */ + await act(async () => { + find('addFieldButton').simulate('click'); + }); + component.update(); + const newField = { name: 'someNewField', type: 'text' }; + await addField(newField.name, newField.type); + updatedMappings = { ...updatedMappings, properties: { @@ -348,26 +348,14 @@ describe.skip('Mappings editor: core', () => { }, }; - await act(async () => { - find('addFieldButton').simulate('click'); - }); - component.update(); - - await act(async () => { - addField(newField.name, newField.type); - }); - component.update(); - ({ data } = await getMappingsEditorData(component)); + expect(data).toEqual(updatedMappings); /** * Dynamic templates */ - await act(async () => { - await selectTab('templates'); - }); - component.update(); + await selectTab('templates'); const updatedTemplatesValue = [{ someTemplateProp: 'updated' }]; updatedMappings = { @@ -385,10 +373,7 @@ describe.skip('Mappings editor: core', () => { /** * Advanced settings */ - await act(async () => { - selectTab('advanced'); - }); - component.update(); + await selectTab('advanced'); // Disbable dynamic mappings await act(async () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx index dc52a362008c60..1457c4583aa0e8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx @@ -71,9 +71,8 @@ export const AnalyzerParameter = ({ allowsIndexDefaultOption = true, 'data-test-subj': dataTestSubj, }: Props) => { - const indexSettings = useIndexSettings(); + const { value: indexSettings } = useIndexSettings(); const customAnalyzers = getCustomAnalyzers(indexSettings); - const analyzerOptions = allowsIndexDefaultOption ? ANALYZER_OPTIONS : ANALYZER_OPTIONS_WITHOUT_DEFAULT; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx index 17d3ea0909bfbf..c966df82fb5072 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx @@ -49,16 +49,17 @@ export const AnalyzerParameterSelects = ({ 'data-test-subj': dataTestSubj, }: Props) => { const { form } = useForm({ defaultValue: { main: mainDefaultValue, sub: subDefaultValue } }); + const { subscribe } = form; useEffect(() => { - const subscription = form.subscribe((updateData) => { + const subscription = subscribe((updateData) => { const formData = updateData.data.raw; const value = formData.sub ? formData.sub : formData.main; onChange(value); }); return subscription.unsubscribe; - }, [form, onChange]); + }, [subscribe, onChange]); const getSubOptionsMeta = useCallback( (mainValue: string) => diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/index_settings_context.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/index_settings_context.tsx index 411193f10b24af..bd84c3a905ec8d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/index_settings_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/index_settings_context.tsx @@ -3,23 +3,32 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useState } from 'react'; import { IndexSettings } from './types'; -const IndexSettingsContext = createContext(undefined); +const IndexSettingsContext = createContext< + { value: IndexSettings; update: (value: IndexSettings) => void } | undefined +>(undefined); interface Props { - indexSettings: IndexSettings | undefined; children: React.ReactNode; } -export const IndexSettingsProvider = ({ indexSettings = {}, children }: Props) => ( - {children} -); +export const IndexSettingsProvider = ({ children }: Props) => { + const [state, setState] = useState({}); + + return ( + + {children} + + ); +}; export const useIndexSettings = () => { const ctx = useContext(IndexSettingsContext); - - return ctx === undefined ? {} : ctx; + if (ctx === undefined) { + throw new Error('useIndexSettings must be used within a '); + } + return ctx; }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index 39451639bfb864..39c4a2885efa5c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -25,7 +25,7 @@ import { import { extractMappingsDefinition } from './lib'; import { useMappingsState } from './mappings_state_context'; import { useMappingsStateListener } from './use_state_listener'; -import { IndexSettingsProvider } from './index_settings_context'; +import { useIndexSettings } from './index_settings_context'; type TabName = 'fields' | 'advanced' | 'templates'; @@ -94,6 +94,12 @@ export const MappingsEditor = React.memo(({ onChange, value, indexSettings }: Pr */ useMappingsStateListener({ onChange, value: parsedDefaultValue }); + // Update the Index settings context so it is available in the Global flyout + const { update: updateIndexSettings } = useIndexSettings(); + if (indexSettings !== undefined) { + updateIndexSettings(indexSettings); + } + const state = useMappingsState(); const [selectedTab, selectTab] = useState('fields'); @@ -141,43 +147,41 @@ export const MappingsEditor = React.memo(({ onChange, value, indexSettings }: Pr {multipleMappingsDeclared ? ( ) : ( - -

- - changeTab('fields')} - isSelected={selectedTab === 'fields'} - data-test-subj="formTab" - > - {i18n.translate('xpack.idxMgmt.mappingsEditor.fieldsTabLabel', { - defaultMessage: 'Mapped fields', - })} - - changeTab('templates')} - isSelected={selectedTab === 'templates'} - data-test-subj="formTab" - > - {i18n.translate('xpack.idxMgmt.mappingsEditor.templatesTabLabel', { - defaultMessage: 'Dynamic templates', - })} - - changeTab('advanced')} - isSelected={selectedTab === 'advanced'} - data-test-subj="formTab" - > - {i18n.translate('xpack.idxMgmt.mappingsEditor.advancedTabLabel', { - defaultMessage: 'Advanced options', - })} - - - - - - {tabToContentMap[selectedTab]} -
- +
+ + changeTab('fields')} + isSelected={selectedTab === 'fields'} + data-test-subj="formTab" + > + {i18n.translate('xpack.idxMgmt.mappingsEditor.fieldsTabLabel', { + defaultMessage: 'Mapped fields', + })} + + changeTab('templates')} + isSelected={selectedTab === 'templates'} + data-test-subj="formTab" + > + {i18n.translate('xpack.idxMgmt.mappingsEditor.templatesTabLabel', { + defaultMessage: 'Dynamic templates', + })} + + changeTab('advanced')} + isSelected={selectedTab === 'advanced'} + data-test-subj="formTab" + > + {i18n.translate('xpack.idxMgmt.mappingsEditor.advancedTabLabel', { + defaultMessage: 'Advanced options', + })} + + + + + + {tabToContentMap[selectedTab]} +
)}
); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor_context.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor_context.tsx index 596b49cc89ee80..8e30d07c2262f2 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor_context.tsx @@ -6,7 +6,12 @@ import React from 'react'; import { StateProvider } from './mappings_state_context'; +import { IndexSettingsProvider } from './index_settings_context'; export const MappingsEditorProvider: React.FC = ({ children }) => { - return {children}; + return ( + + {children} + + ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts index 2a8368c666859e..e7efd6f28343bc 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts @@ -175,10 +175,18 @@ export const reducer = (state: State, action: Action): State => { fields: action.value.fields, configuration: { ...state.configuration, + data: { + raw: action.value.configuration, + format: () => action.value.configuration, + }, defaultValue: action.value.configuration, }, templates: { ...state.templates, + data: { + raw: action.value.templates, + format: () => action.value.templates, + }, defaultValue: action.value.templates, }, documentFields: { diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts index 3b9de2b3409b65..ac6e8b7879a261 100644 --- a/x-pack/plugins/index_management/test/fixtures/template.ts +++ b/x-pack/plugins/index_management/test/fixtures/template.ts @@ -11,6 +11,42 @@ const objHasProperties = (obj?: Record): boolean => { return obj === undefined || Object.keys(obj).length === 0 ? false : true; }; +export const getComposableTemplate = ({ + name = getRandomString(), + version = getRandomNumber(), + priority = getRandomNumber(), + indexPatterns = [], + template: { settings, aliases, mappings } = {}, + hasDatastream = false, + isLegacy = false, + type = 'default', +}: Partial< + TemplateDeserialized & { + isLegacy?: boolean; + type?: TemplateType; + hasDatastream: boolean; + } +> = {}): TemplateDeserialized => { + const indexTemplate = { + name, + version, + priority, + indexPatterns, + template: { + aliases, + mappings, + settings, + }, + _kbnMeta: { + type, + hasDatastream, + isLegacy, + }, + }; + + return indexTemplate; +}; + export const getTemplate = ({ name = getRandomString(), version = getRandomNumber(),