From 573409b9f04031bac7be889d2c4bb61f207242e4 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 3 Jun 2020 16:49:38 -0700 Subject: [PATCH] Refactor index management client integration tests for scalability (#67917) --- .../client_integration/helpers/index.ts | 50 +- .../helpers/setup_environment.tsx | 1 + .../__jest__/client_integration/home.test.ts | 587 ------------------ .../client_integration/home/home.helpers.ts | 46 ++ .../client_integration/home/home.test.ts | 79 +++ .../index_templates_tab.helpers.ts} | 85 +-- .../home/index_templates_tab.test.ts | 463 ++++++++++++++ .../home/indices_tab.helpers.ts | 70 +++ .../home/indices_tab.test.ts | 105 ++++ .../constants.ts | 0 .../template_clone.helpers.ts | 8 +- .../template_clone.test.tsx | 17 +- .../template_create.helpers.ts | 8 +- .../template_create.test.tsx | 15 +- .../template_edit.helpers.ts | 8 +- .../template_edit.test.tsx | 15 +- .../template_form.helpers.ts | 3 +- .../__jest__/components/index_table.test.js | 2 - .../plugins/index_management/__mocks__/ace.js | 45 -- .../__mocks__/ui/documentation_links.js | 7 - .../index_management/__mocks__/ui/notify.js | 15 - .../__snapshots__/flatten_object.test.js.snap | 0 .../application}/lib/flatten_object.test.js | 4 +- .../sections/home/index_list/index.ts | 2 +- 24 files changed, 850 insertions(+), 785 deletions(-) delete mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home.test.ts create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts rename x-pack/plugins/index_management/__jest__/client_integration/{helpers/home.helpers.ts => home/index_templates_tab.helpers.ts} (61%) create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts rename x-pack/plugins/index_management/__jest__/client_integration/{helpers => index_template_wizard}/constants.ts (100%) rename x-pack/plugins/index_management/__jest__/client_integration/{helpers => index_template_wizard}/template_clone.helpers.ts (76%) rename x-pack/plugins/index_management/__jest__/client_integration/{ => index_template_wizard}/template_clone.test.tsx (88%) rename x-pack/plugins/index_management/__jest__/client_integration/{helpers => index_template_wizard}/template_create.helpers.ts (77%) rename x-pack/plugins/index_management/__jest__/client_integration/{ => index_template_wizard}/template_create.test.tsx (97%) rename x-pack/plugins/index_management/__jest__/client_integration/{helpers => index_template_wizard}/template_edit.helpers.ts (77%) rename x-pack/plugins/index_management/__jest__/client_integration/{ => index_template_wizard}/template_edit.test.tsx (95%) rename x-pack/plugins/index_management/__jest__/client_integration/{helpers => index_template_wizard}/template_form.helpers.ts (99%) delete mode 100644 x-pack/plugins/index_management/__mocks__/ace.js delete mode 100644 x-pack/plugins/index_management/__mocks__/ui/documentation_links.js delete mode 100644 x-pack/plugins/index_management/__mocks__/ui/notify.js rename x-pack/plugins/index_management/{__jest__ => public/application}/lib/__snapshots__/flatten_object.test.js.snap (100%) rename x-pack/plugins/index_management/{__jest__ => public/application}/lib/flatten_object.test.js (90%) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts index 66021b531919a9..ab42a035a3d903 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts @@ -4,18 +4,44 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setup as homeSetup } from './home.helpers'; -import { setup as templateCreateSetup } from './template_create.helpers'; -import { setup as templateCloneSetup } from './template_clone.helpers'; -import { setup as templateEditSetup } from './template_edit.helpers'; - export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../../../test_utils'; -export { setupEnvironment } from './setup_environment'; +export { setupEnvironment, WithAppDependencies, services } from './setup_environment'; -export const pageHelpers = { - home: { setup: homeSetup }, - templateCreate: { setup: templateCreateSetup }, - templateClone: { setup: templateCloneSetup }, - templateEdit: { setup: templateEditSetup }, -}; +export type TestSubjects = + | 'aliasesTab' + | 'appTitle' + | 'cell' + | 'closeDetailsButton' + | 'createTemplateButton' + | 'deleteSystemTemplateCallOut' + | 'deleteTemplateButton' + | 'deleteTemplatesConfirmation' + | 'documentationLink' + | 'emptyPrompt' + | 'manageTemplateButton' + | 'mappingsTab' + | 'noAliasesCallout' + | 'noMappingsCallout' + | 'noSettingsCallout' + | 'indicesList' + | 'indicesTab' + | 'indexTableIncludeHiddenIndicesToggle' + | 'indexTableIndexNameLink' + | 'reloadButton' + | 'reloadIndicesButton' + | 'row' + | 'sectionError' + | 'sectionLoading' + | 'settingsTab' + | 'summaryTab' + | 'summaryTitle' + | 'systemTemplatesSwitch' + | 'templateDetails' + | 'templateDetails.manageTemplateButton' + | 'templateDetails.sectionLoading' + | 'templateDetails.tab' + | 'templateDetails.title' + | 'templateList' + | 'templateTable' + | 'templatesTab'; 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 1eaf7efd173954..0a49191fdb1496 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 @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + /* eslint-disable @kbn/eslint/no-restricted-paths */ import React from 'react'; import axios from 'axios'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts deleted file mode 100644 index f5af11330a6d86..00000000000000 --- a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts +++ /dev/null @@ -1,587 +0,0 @@ -/* - * 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. - */ - -import { act } from 'react-dom/test-utils'; -import * as fixtures from '../../test/fixtures'; -import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers'; -import { IdxMgmtHomeTestBed } from './helpers/home.helpers'; -import { API_BASE_PATH } from '../../common/constants'; - -const { setup } = pageHelpers.home; - -const removeWhiteSpaceOnArrayValues = (array: any[]) => - array.map((value) => { - if (!value.trim) { - return value; - } - return value.trim(); - }); - -jest.mock('ui/new_platform'); - -describe('', () => { - const { server, httpRequestsMockHelpers } = setupEnvironment(); - let testBed: IdxMgmtHomeTestBed; - - afterAll(() => { - server.restore(); - }); - - describe('on component mount', () => { - beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndicesResponse([]); - - testBed = await setup(); - - await act(async () => { - const { component } = testBed; - - await nextTick(); - component.update(); - }); - }); - - test('sets the hash query param base on include hidden indices toggle', () => { - const { actions } = testBed; - expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); - expect(window.location.hash.includes('includeHidden=true')).toBe(true); - actions.clickIncludeHiddenIndicesToggle(); - expect(window.location.hash.includes('includeHidden=true')).toBe(false); - // Note: this test modifies the shared location.hash state, we put it back the way it was - actions.clickIncludeHiddenIndicesToggle(); - expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); - expect(window.location.hash.includes('includeHidden=true')).toBe(true); - }); - - test('should set the correct app title', () => { - const { exists, find } = testBed; - expect(exists('appTitle')).toBe(true); - expect(find('appTitle').text()).toEqual('Index Management'); - }); - - test('should have a link to the documentation', () => { - const { exists, find } = testBed; - expect(exists('documentationLink')).toBe(true); - expect(find('documentationLink').text()).toBe('Index Management docs'); - }); - - describe('tabs', () => { - test('should have 2 tabs', () => { - const { find } = testBed; - const templatesTab = find('templatesTab'); - const indicesTab = find('indicesTab'); - - expect(indicesTab.length).toBe(1); - expect(indicesTab.text()).toEqual('Indices'); - expect(templatesTab.length).toBe(1); - expect(templatesTab.text()).toEqual('Index Templates'); - }); - - test('should navigate to Index Templates tab', async () => { - const { exists, actions, component } = testBed; - - expect(exists('indicesList')).toBe(true); - expect(exists('templateList')).toBe(false); - - httpRequestsMockHelpers.setLoadTemplatesResponse([]); - - actions.selectHomeTab('templatesTab'); - - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(exists('indicesList')).toBe(false); - expect(exists('templateList')).toBe(true); - }); - }); - - describe('index templates', () => { - describe('when there are no index templates', () => { - beforeEach(async () => { - const { actions, component } = testBed; - - httpRequestsMockHelpers.setLoadTemplatesResponse([]); - - actions.selectHomeTab('templatesTab'); - - await act(async () => { - await nextTick(); - component.update(); - }); - }); - - test('should display an empty prompt', async () => { - const { exists } = testBed; - - expect(exists('sectionLoading')).toBe(false); - expect(exists('emptyPrompt')).toBe(true); - }); - }); - - describe('when there are index templates', () => { - const template1 = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - template: { - settings: { - index: { - number_of_shards: '1', - lifecycle: { - name: 'my_ilm_policy', - }, - }, - }, - }, - }); - const template2 = fixtures.getTemplate({ - name: `b${getRandomString()}`, - indexPatterns: ['template2Pattern1*'], - }); - const template3 = fixtures.getTemplate({ - name: `.c${getRandomString()}`, // mock system template - indexPatterns: ['template3Pattern1*', 'template3Pattern2', 'template3Pattern3'], - }); - - const templates = [template1, template2, template3]; - - beforeEach(async () => { - const { actions, component } = testBed; - - httpRequestsMockHelpers.setLoadTemplatesResponse(templates); - - actions.selectHomeTab('templatesTab'); - - await act(async () => { - await nextTick(); - component.update(); - }); - }); - - test('should list them in the table', async () => { - const { table } = testBed; - - const { tableCellsValues } = table.getMetaData('templateTable'); - - tableCellsValues.forEach((row, i) => { - const template = templates[i]; - const { name, indexPatterns, order, ilmPolicy } = template; - - const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : ''; - const orderFormatted = order ? order.toString() : order; - - expect(removeWhiteSpaceOnArrayValues(row)).toEqual([ - '', - name, - indexPatterns.join(', '), - ilmPolicyName, - orderFormatted, - '', - '', - '', - '', - ]); - }); - }); - - test('should have a button to reload the index templates', async () => { - const { component, exists, actions } = testBed; - const totalRequests = server.requests.length; - - expect(exists('reloadButton')).toBe(true); - - await act(async () => { - actions.clickReloadButton(); - await nextTick(); - component.update(); - }); - - expect(server.requests.length).toBe(totalRequests + 1); - expect(server.requests[server.requests.length - 1].url).toBe( - `${API_BASE_PATH}/templates` - ); - }); - - test('should have a button to create a new template', () => { - const { exists } = testBed; - expect(exists('createTemplateButton')).toBe(true); - }); - - test('should have a switch to view system templates', async () => { - const { table, exists, component, form } = testBed; - const { rows } = table.getMetaData('templateTable'); - - expect(rows.length).toEqual( - templates.filter((template) => !template.name.startsWith('.')).length - ); - - expect(exists('systemTemplatesSwitch')).toBe(true); - - await act(async () => { - form.toggleEuiSwitch('systemTemplatesSwitch'); - await nextTick(); - component.update(); - }); - - const { rows: updatedRows } = table.getMetaData('templateTable'); - expect(updatedRows.length).toEqual(templates.length); - }); - - test('each row should have a link to the template details panel', async () => { - const { find, exists, actions } = testBed; - - await actions.clickTemplateAt(0); - - expect(exists('templateList')).toBe(true); - expect(exists('templateDetails')).toBe(true); - expect(find('templateDetails.title').text()).toBe(template1.name); - }); - - test('template actions column should have an option to delete', () => { - const { actions, findAction } = testBed; - const { name: templateName } = template1; - - actions.clickActionMenu(templateName); - - const deleteAction = findAction('delete'); - - expect(deleteAction.text()).toEqual('Delete'); - }); - - test('template actions column should have an option to clone', () => { - const { actions, findAction } = testBed; - const { name: templateName } = template1; - - actions.clickActionMenu(templateName); - - const cloneAction = findAction('clone'); - - expect(cloneAction.text()).toEqual('Clone'); - }); - - test('template actions column should have an option to edit', () => { - const { actions, findAction } = testBed; - const { name: templateName } = template1; - - actions.clickActionMenu(templateName); - - const editAction = findAction('edit'); - - expect(editAction.text()).toEqual('Edit'); - }); - - describe('delete index template', () => { - test('should show a confirmation when clicking the delete template button', async () => { - const { actions } = testBed; - const { name: templateName } = template1; - - await actions.clickTemplateAction(templateName, 'delete'); - - // We need to read the document "body" as the modal is added there and not inside - // the component DOM tree. - expect( - document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]') - ).not.toBe(null); - - expect( - document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]')! - .textContent - ).toContain('Delete template'); - }); - - test('should show a warning message when attempting to delete a system template', async () => { - const { component, form, actions } = testBed; - - await act(async () => { - form.toggleEuiSwitch('systemTemplatesSwitch'); - await nextTick(); - component.update(); - }); - - const { name: systemTemplateName } = template3; - await actions.clickTemplateAction(systemTemplateName, 'delete'); - - expect( - document.body.querySelector('[data-test-subj="deleteSystemTemplateCallOut"]') - ).not.toBe(null); - }); - - test('should send the correct HTTP request to delete an index template', async () => { - const { component, actions, table } = testBed; - const { rows } = table.getMetaData('templateTable'); - - const templateId = rows[0].columns[2].value; - - const { - name: templateName, - _kbnMeta: { formatVersion }, - } = template1; - await actions.clickTemplateAction(templateName, 'delete'); - - const modal = document.body.querySelector( - '[data-test-subj="deleteTemplatesConfirmation"]' - ); - const confirmButton: HTMLButtonElement | null = modal!.querySelector( - '[data-test-subj="confirmModalConfirmButton"]' - ); - - httpRequestsMockHelpers.setDeleteTemplateResponse({ - results: { - successes: [templateId], - errors: [], - }, - }); - - await act(async () => { - confirmButton!.click(); - await nextTick(); - component.update(); - }); - - const latestRequest = server.requests[server.requests.length - 1]; - - expect(latestRequest.method).toBe('POST'); - expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-templates`); - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ - templates: [{ name: template1.name, formatVersion }], - }); - }); - }); - - describe('detail panel', () => { - beforeEach(async () => { - const template = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - }); - - httpRequestsMockHelpers.setLoadTemplateResponse(template); - }); - - test('should show details when clicking on a template', async () => { - const { exists, actions } = testBed; - - expect(exists('templateDetails')).toBe(false); - - await actions.clickTemplateAt(0); - - expect(exists('templateDetails')).toBe(true); - }); - - describe('on mount', () => { - beforeEach(async () => { - const { actions } = testBed; - - await actions.clickTemplateAt(0); - }); - - test('should set the correct title', async () => { - const { find } = testBed; - const { name } = template1; - - expect(find('templateDetails.title').text()).toEqual(name); - }); - - it('should have a close button and be able to close flyout', async () => { - const { actions, component, exists } = testBed; - - expect(exists('closeDetailsButton')).toBe(true); - expect(exists('summaryTab')).toBe(true); - - actions.clickCloseDetailsButton(); - - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(exists('summaryTab')).toBe(false); - }); - - it('should have a manage button', async () => { - const { actions, exists } = testBed; - - await actions.clickTemplateAt(0); - - expect(exists('templateDetails.manageTemplateButton')).toBe(true); - }); - }); - - describe('tabs', () => { - test('should have 4 tabs', async () => { - const template = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - template: { - settings: { - index: { - number_of_shards: '1', - }, - }, - mappings: { - _source: { - enabled: false, - }, - properties: { - created_at: { - type: 'date', - format: 'EEE MMM dd HH:mm:ss Z yyyy', - }, - }, - }, - aliases: { - alias1: {}, - }, - }, - }); - - const { find, actions, exists } = testBed; - - httpRequestsMockHelpers.setLoadTemplateResponse(template); - - await actions.clickTemplateAt(0); - - expect(find('templateDetails.tab').length).toBe(4); - expect(find('templateDetails.tab').map((t) => t.text())).toEqual([ - 'Summary', - 'Settings', - 'Mappings', - 'Aliases', - ]); - - // Summary tab should be initial active tab - expect(exists('summaryTab')).toBe(true); - - // Navigate and verify all tabs - actions.selectDetailsTab('settings'); - expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(true); - - actions.selectDetailsTab('aliases'); - expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(false); - expect(exists('aliasesTab')).toBe(true); - - actions.selectDetailsTab('mappings'); - expect(exists('summaryTab')).toBe(false); - expect(exists('settingsTab')).toBe(false); - expect(exists('aliasesTab')).toBe(false); - expect(exists('mappingsTab')).toBe(true); - }); - - test('should show an info callout if data is not present', async () => { - const templateWithNoOptionalFields = fixtures.getTemplate({ - name: `a${getRandomString()}`, - indexPatterns: ['template1Pattern1*', 'template1Pattern2'], - }); - - const { actions, find, exists, component } = testBed; - - httpRequestsMockHelpers.setLoadTemplateResponse(templateWithNoOptionalFields); - - await actions.clickTemplateAt(0); - - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(find('templateDetails.tab').length).toBe(4); - expect(exists('summaryTab')).toBe(true); - - // Navigate and verify callout message per tab - actions.selectDetailsTab('settings'); - expect(exists('noSettingsCallout')).toBe(true); - - actions.selectDetailsTab('mappings'); - expect(exists('noMappingsCallout')).toBe(true); - - actions.selectDetailsTab('aliases'); - expect(exists('noAliasesCallout')).toBe(true); - }); - }); - - describe('error handling', () => { - it('should render an error message if error fetching template details', async () => { - const { actions, exists } = testBed; - const error = { - status: 404, - error: 'Not found', - message: 'Template not found', - }; - - httpRequestsMockHelpers.setLoadTemplateResponse(undefined, { body: error }); - - await actions.clickTemplateAt(0); - - expect(exists('sectionError')).toBe(true); - // Manage button should not render if error - expect(exists('templateDetails.manageTemplateButton')).toBe(false); - }); - }); - }); - }); - }); - }); - - describe('index detail panel with % character in index name', () => { - const indexName = 'test%'; - beforeEach(async () => { - const index = { - health: 'green', - status: 'open', - primary: 1, - replica: 1, - documents: 10000, - documents_deleted: 100, - size: '156kb', - primary_size: '156kb', - name: indexName, - }; - httpRequestsMockHelpers.setLoadIndicesResponse([index]); - - testBed = await setup(); - const { component, find } = testBed; - - component.update(); - - find('indexTableIndexNameLink').at(0).simulate('click'); - }); - - test('should encode indexName when loading settings in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('settings'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); - }); - - test('should encode indexName when loading mappings in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('mappings'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/mapping/${encodeURIComponent(indexName)}`); - }); - - test('should encode indexName when loading stats in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('stats'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/stats/${encodeURIComponent(indexName)}`); - }); - - test('should encode indexName when editing settings in detail panel', async () => { - const { actions } = testBed; - await actions.selectIndexDetailsTab('edit_settings'); - - const latestRequest = server.requests[server.requests.length - 1]; - expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); - }); - }); -}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts new file mode 100644 index 00000000000000..c58109364890a1 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: TestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/indices?includeHidden=true`], + componentRoutePath: `/:section(indices|templates)`, + }, + doMountAsync: true, +}; + +const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); + +export interface HomeTestBed extends TestBed { + actions: { + selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void; + }; +} + +export const setup = async (): Promise => { + const testBed = await initTestBed(); + + /** + * User Actions + */ + + const selectHomeTab = (tab: 'indicesTab' | 'templatesTab') => { + testBed.find(tab).simulate('click'); + }; + + return { + ...testBed, + actions: { + selectHomeTab, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts new file mode 100644 index 00000000000000..d195ce46c2f540 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts @@ -0,0 +1,79 @@ +/* + * 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. + */ + +import { act } from 'react-dom/test-utils'; + +import { setupEnvironment, nextTick } from '../helpers'; + +import { HomeTestBed, setup } from './home.helpers'; + +describe('', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: HomeTestBed; + + afterAll(() => { + server.restore(); + }); + + describe('on component mount', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + testBed = await setup(); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + test('should set the correct app title', () => { + const { exists, find } = testBed; + expect(exists('appTitle')).toBe(true); + expect(find('appTitle').text()).toEqual('Index Management'); + }); + + test('should have a link to the documentation', () => { + const { exists, find } = testBed; + expect(exists('documentationLink')).toBe(true); + expect(find('documentationLink').text()).toBe('Index Management docs'); + }); + + describe('tabs', () => { + test('should have 2 tabs', () => { + const { find } = testBed; + const templatesTab = find('templatesTab'); + const indicesTab = find('indicesTab'); + + expect(indicesTab.length).toBe(1); + expect(indicesTab.text()).toEqual('Indices'); + expect(templatesTab.length).toBe(1); + expect(templatesTab.text()).toEqual('Index Templates'); + }); + + test('should navigate to Index Templates tab', async () => { + const { exists, actions, component } = testBed; + + expect(exists('indicesList')).toBe(true); + expect(exists('templateList')).toBe(false); + + httpRequestsMockHelpers.setLoadTemplatesResponse([]); + + actions.selectHomeTab('templatesTab'); + + await act(async () => { + await nextTick(); + component.update(); + }); + + expect(exists('indicesList')).toBe(false); + expect(exists('templateList')).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts similarity index 61% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts index 36d8e1d343b5ea..0c4cca4dbcc7ef 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts @@ -6,6 +6,7 @@ import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; + import { registerTestBed, TestBed, @@ -13,10 +14,12 @@ import { findTestSubject, nextTick, } from '../../../../../test_utils'; +// NOTE: We have to use the Home component instead of the TemplateList component because we depend +// upon react router to provide the name of the template to load in the detail panel. import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { TemplateDeserialized } from '../../../common'; -import { WithAppDependencies, services } from './setup_environment'; +import { WithAppDependencies, services, TestSubjects } from '../helpers'; const testBedConfig: TestBedConfig = { store: () => indexManagementStore(services as any), @@ -29,12 +32,11 @@ const testBedConfig: TestBedConfig = { const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); -export interface IdxMgmtHomeTestBed extends TestBed { +export interface IndexTemplatesTabTestBed extends TestBed { findAction: (action: 'edit' | 'clone' | 'delete') => ReactWrapper; actions: { - selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void; + goToTemplatesList: () => void; selectDetailsTab: (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => void; - selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; clickReloadButton: () => void; clickTemplateAction: ( name: TemplateDeserialized['name'], @@ -43,12 +45,10 @@ export interface IdxMgmtHomeTestBed extends TestBed { clickTemplateAt: (index: number) => void; clickCloseDetailsButton: () => void; clickActionMenu: (name: TemplateDeserialized['name']) => void; - getIncludeHiddenIndicesToggleStatus: () => boolean; - clickIncludeHiddenIndicesToggle: () => void; }; } -export const setup = async (): Promise => { +export const setup = async (): Promise => { const testBed = await initTestBed(); /** @@ -65,8 +65,8 @@ export const setup = async (): Promise => { * User Actions */ - const selectHomeTab = (tab: 'indicesTab' | 'templatesTab') => { - testBed.find(tab).simulate('click'); + const goToTemplatesList = () => { + testBed.find('templatesTab').simulate('click'); }; const selectDetailsTab = (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => { @@ -119,82 +119,17 @@ export const setup = async (): Promise => { find('closeDetailsButton').simulate('click'); }; - const clickIncludeHiddenIndicesToggle = () => { - const { find } = testBed; - find('indexTableIncludeHiddenIndicesToggle').simulate('click'); - }; - - const getIncludeHiddenIndicesToggleStatus = () => { - const { find } = testBed; - const props = find('indexTableIncludeHiddenIndicesToggle').props(); - return Boolean(props['aria-checked']); - }; - - const selectIndexDetailsTab = async ( - tab: 'settings' | 'mappings' | 'stats' | 'edit_settings' - ) => { - const indexDetailsTabs = ['settings', 'mappings', 'stats', 'edit_settings']; - const { find, component } = testBed; - await act(async () => { - find('detailPanelTab').at(indexDetailsTabs.indexOf(tab)).simulate('click'); - }); - component.update(); - }; - return { ...testBed, findAction, actions: { - selectHomeTab, + goToTemplatesList, selectDetailsTab, - selectIndexDetailsTab, clickReloadButton, clickTemplateAction, clickTemplateAt, clickCloseDetailsButton, clickActionMenu, - getIncludeHiddenIndicesToggleStatus, - clickIncludeHiddenIndicesToggle, }, }; }; - -type IdxMgmtTestSubjects = TestSubjects; - -export type TestSubjects = - | 'aliasesTab' - | 'appTitle' - | 'cell' - | 'closeDetailsButton' - | 'createTemplateButton' - | 'deleteSystemTemplateCallOut' - | 'deleteTemplateButton' - | 'deleteTemplatesConfirmation' - | 'documentationLink' - | 'emptyPrompt' - | 'manageTemplateButton' - | 'mappingsTab' - | 'noAliasesCallout' - | 'noMappingsCallout' - | 'noSettingsCallout' - | 'indicesList' - | 'indicesTab' - | 'indexTableIncludeHiddenIndicesToggle' - | 'indexTableIndexNameLink' - | 'reloadButton' - | 'reloadIndicesButton' - | 'row' - | 'sectionError' - | 'sectionLoading' - | 'settingsTab' - | 'summaryTab' - | 'summaryTitle' - | 'systemTemplatesSwitch' - | 'templateDetails' - | 'templateDetails.manageTemplateButton' - | 'templateDetails.sectionLoading' - | 'templateDetails.tab' - | 'templateDetails.title' - | 'templateList' - | 'templateTable' - | 'templatesTab'; 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 new file mode 100644 index 00000000000000..c9a279e90d0e0a --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -0,0 +1,463 @@ +/* + * 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. + */ + +import { act } from 'react-dom/test-utils'; + +import * as fixtures from '../../../test/fixtures'; +import { API_BASE_PATH } from '../../../common/constants'; +import { setupEnvironment, nextTick, getRandomString } from '../helpers'; + +import { IndexTemplatesTabTestBed, setup } from './index_templates_tab.helpers'; + +const removeWhiteSpaceOnArrayValues = (array: any[]) => + array.map((value) => { + if (!value.trim) { + return value; + } + return value.trim(); + }); + +describe('Index Templates tab', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: IndexTemplatesTabTestBed; + + afterAll(() => { + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + testBed = await setup(); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + describe('when there are no index templates', () => { + beforeEach(async () => { + const { actions, component } = testBed; + + httpRequestsMockHelpers.setLoadTemplatesResponse([]); + + actions.goToTemplatesList(); + + await act(async () => { + await nextTick(); + component.update(); + }); + }); + + test('should display an empty prompt', async () => { + const { exists } = testBed; + + expect(exists('sectionLoading')).toBe(false); + expect(exists('emptyPrompt')).toBe(true); + }); + }); + + describe('when there are index templates', () => { + const template1 = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + template: { + settings: { + index: { + number_of_shards: '1', + lifecycle: { + name: 'my_ilm_policy', + }, + }, + }, + }, + }); + const template2 = fixtures.getTemplate({ + name: `b${getRandomString()}`, + indexPatterns: ['template2Pattern1*'], + }); + const template3 = fixtures.getTemplate({ + name: `.c${getRandomString()}`, // mock system template + indexPatterns: ['template3Pattern1*', 'template3Pattern2', 'template3Pattern3'], + }); + + const templates = [template1, template2, template3]; + + beforeEach(async () => { + const { actions, component } = testBed; + + httpRequestsMockHelpers.setLoadTemplatesResponse(templates); + + actions.goToTemplatesList(); + + await act(async () => { + await nextTick(); + component.update(); + }); + }); + + test('should list them in the table', async () => { + const { table } = testBed; + + const { tableCellsValues } = table.getMetaData('templateTable'); + + tableCellsValues.forEach((row, i) => { + const template = templates[i]; + const { name, indexPatterns, order, ilmPolicy } = template; + + const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : ''; + const orderFormatted = order ? order.toString() : order; + + expect(removeWhiteSpaceOnArrayValues(row)).toEqual([ + '', + name, + indexPatterns.join(', '), + ilmPolicyName, + orderFormatted, + '', + '', + '', + '', + ]); + }); + }); + + test('should have a button to reload the index templates', async () => { + const { component, exists, actions } = testBed; + const totalRequests = server.requests.length; + + expect(exists('reloadButton')).toBe(true); + + await act(async () => { + actions.clickReloadButton(); + await nextTick(); + component.update(); + }); + + expect(server.requests.length).toBe(totalRequests + 1); + expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/templates`); + }); + + test('should have a button to create a new template', () => { + const { exists } = testBed; + expect(exists('createTemplateButton')).toBe(true); + }); + + test('should have a switch to view system templates', async () => { + const { table, exists, component, form } = testBed; + const { rows } = table.getMetaData('templateTable'); + + expect(rows.length).toEqual( + templates.filter((template) => !template.name.startsWith('.')).length + ); + + expect(exists('systemTemplatesSwitch')).toBe(true); + + await act(async () => { + form.toggleEuiSwitch('systemTemplatesSwitch'); + await nextTick(); + component.update(); + }); + + const { rows: updatedRows } = table.getMetaData('templateTable'); + expect(updatedRows.length).toEqual(templates.length); + }); + + test('each row should have a link to the template details panel', async () => { + const { find, exists, actions } = testBed; + + await actions.clickTemplateAt(0); + + expect(exists('templateList')).toBe(true); + expect(exists('templateDetails')).toBe(true); + expect(find('templateDetails.title').text()).toBe(template1.name); + }); + + test('template actions column should have an option to delete', () => { + const { actions, findAction } = testBed; + const { name: templateName } = template1; + + actions.clickActionMenu(templateName); + + const deleteAction = findAction('delete'); + + expect(deleteAction.text()).toEqual('Delete'); + }); + + test('template actions column should have an option to clone', () => { + const { actions, findAction } = testBed; + const { name: templateName } = template1; + + actions.clickActionMenu(templateName); + + const cloneAction = findAction('clone'); + + expect(cloneAction.text()).toEqual('Clone'); + }); + + test('template actions column should have an option to edit', () => { + const { actions, findAction } = testBed; + const { name: templateName } = template1; + + actions.clickActionMenu(templateName); + + const editAction = findAction('edit'); + + expect(editAction.text()).toEqual('Edit'); + }); + + describe('delete index template', () => { + test('should show a confirmation when clicking the delete template button', async () => { + const { actions } = testBed; + const { name: templateName } = template1; + + await actions.clickTemplateAction(templateName, 'delete'); + + // We need to read the document "body" as the modal is added there and not inside + // the component DOM tree. + expect( + document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]') + ).not.toBe(null); + + expect( + document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]')!.textContent + ).toContain('Delete template'); + }); + + test('should show a warning message when attempting to delete a system template', async () => { + const { component, form, actions } = testBed; + + await act(async () => { + form.toggleEuiSwitch('systemTemplatesSwitch'); + await nextTick(); + component.update(); + }); + + const { name: systemTemplateName } = template3; + await actions.clickTemplateAction(systemTemplateName, 'delete'); + + expect( + document.body.querySelector('[data-test-subj="deleteSystemTemplateCallOut"]') + ).not.toBe(null); + }); + + test('should send the correct HTTP request to delete an index template', async () => { + const { component, actions, table } = testBed; + const { rows } = table.getMetaData('templateTable'); + + const templateId = rows[0].columns[2].value; + + const { + name: templateName, + _kbnMeta: { formatVersion }, + } = template1; + await actions.clickTemplateAction(templateName, 'delete'); + + const modal = document.body.querySelector('[data-test-subj="deleteTemplatesConfirmation"]'); + const confirmButton: HTMLButtonElement | null = modal!.querySelector( + '[data-test-subj="confirmModalConfirmButton"]' + ); + + httpRequestsMockHelpers.setDeleteTemplateResponse({ + results: { + successes: [templateId], + errors: [], + }, + }); + + await act(async () => { + confirmButton!.click(); + await nextTick(); + component.update(); + }); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(latestRequest.method).toBe('POST'); + expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-templates`); + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ + templates: [{ name: template1.name, formatVersion }], + }); + }); + }); + + describe('detail panel', () => { + beforeEach(async () => { + const template = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + }); + + httpRequestsMockHelpers.setLoadTemplateResponse(template); + }); + + test('should show details when clicking on a template', async () => { + const { exists, actions } = testBed; + + expect(exists('templateDetails')).toBe(false); + + await actions.clickTemplateAt(0); + + expect(exists('templateDetails')).toBe(true); + }); + + describe('on mount', () => { + beforeEach(async () => { + const { actions } = testBed; + + await actions.clickTemplateAt(0); + }); + + test('should set the correct title', async () => { + const { find } = testBed; + const { name } = template1; + + expect(find('templateDetails.title').text()).toEqual(name); + }); + + it('should have a close button and be able to close flyout', async () => { + const { actions, component, exists } = testBed; + + expect(exists('closeDetailsButton')).toBe(true); + expect(exists('summaryTab')).toBe(true); + + actions.clickCloseDetailsButton(); + + await act(async () => { + await nextTick(); + component.update(); + }); + + expect(exists('summaryTab')).toBe(false); + }); + + it('should have a manage button', async () => { + const { actions, exists } = testBed; + + await actions.clickTemplateAt(0); + + expect(exists('templateDetails.manageTemplateButton')).toBe(true); + }); + }); + + describe('tabs', () => { + test('should have 4 tabs', async () => { + const template = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + template: { + settings: { + index: { + number_of_shards: '1', + }, + }, + mappings: { + _source: { + enabled: false, + }, + properties: { + created_at: { + type: 'date', + format: 'EEE MMM dd HH:mm:ss Z yyyy', + }, + }, + }, + aliases: { + alias1: {}, + }, + }, + }); + + const { find, actions, exists } = testBed; + + httpRequestsMockHelpers.setLoadTemplateResponse(template); + + await actions.clickTemplateAt(0); + + expect(find('templateDetails.tab').length).toBe(4); + expect(find('templateDetails.tab').map((t) => t.text())).toEqual([ + 'Summary', + 'Settings', + 'Mappings', + 'Aliases', + ]); + + // Summary tab should be initial active tab + expect(exists('summaryTab')).toBe(true); + + // Navigate and verify all tabs + actions.selectDetailsTab('settings'); + expect(exists('summaryTab')).toBe(false); + expect(exists('settingsTab')).toBe(true); + + actions.selectDetailsTab('aliases'); + expect(exists('summaryTab')).toBe(false); + expect(exists('settingsTab')).toBe(false); + expect(exists('aliasesTab')).toBe(true); + + actions.selectDetailsTab('mappings'); + expect(exists('summaryTab')).toBe(false); + expect(exists('settingsTab')).toBe(false); + expect(exists('aliasesTab')).toBe(false); + expect(exists('mappingsTab')).toBe(true); + }); + + test('should show an info callout if data is not present', async () => { + const templateWithNoOptionalFields = fixtures.getTemplate({ + name: `a${getRandomString()}`, + indexPatterns: ['template1Pattern1*', 'template1Pattern2'], + }); + + const { actions, find, exists, component } = testBed; + + httpRequestsMockHelpers.setLoadTemplateResponse(templateWithNoOptionalFields); + + await actions.clickTemplateAt(0); + + await act(async () => { + await nextTick(); + component.update(); + }); + + expect(find('templateDetails.tab').length).toBe(4); + expect(exists('summaryTab')).toBe(true); + + // Navigate and verify callout message per tab + actions.selectDetailsTab('settings'); + expect(exists('noSettingsCallout')).toBe(true); + + actions.selectDetailsTab('mappings'); + expect(exists('noMappingsCallout')).toBe(true); + + actions.selectDetailsTab('aliases'); + expect(exists('noAliasesCallout')).toBe(true); + }); + }); + + describe('error handling', () => { + it('should render an error message if error fetching template details', async () => { + const { actions, exists } = testBed; + const error = { + status: 404, + error: 'Not found', + message: 'Template not found', + }; + + httpRequestsMockHelpers.setLoadTemplateResponse(undefined, { body: error }); + + await actions.clickTemplateAt(0); + + expect(exists('sectionError')).toBe(true); + // Manage button should not render if error + expect(exists('templateDetails.manageTemplateButton')).toBe(false); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts new file mode 100644 index 00000000000000..07f3391782a543 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -0,0 +1,70 @@ +/* + * 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. + */ + +import { act } from 'react-dom/test-utils'; + +import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { IndexList } from '../../../public/application/sections/home/index_list'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: TestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/indices?includeHidden=true`], + componentRoutePath: `/:section(indices|templates)`, + }, + doMountAsync: true, +}; + +const initTestBed = registerTestBed(WithAppDependencies(IndexList), testBedConfig); + +export interface IndicesTestBed extends TestBed { + actions: { + selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; + getIncludeHiddenIndicesToggleStatus: () => boolean; + clickIncludeHiddenIndicesToggle: () => void; + }; +} + +export const setup = async (): Promise => { + const testBed = await initTestBed(); + + /** + * User Actions + */ + + const clickIncludeHiddenIndicesToggle = () => { + const { find } = testBed; + find('indexTableIncludeHiddenIndicesToggle').simulate('click'); + }; + + const getIncludeHiddenIndicesToggleStatus = () => { + const { find } = testBed; + const props = find('indexTableIncludeHiddenIndicesToggle').props(); + return Boolean(props['aria-checked']); + }; + + const selectIndexDetailsTab = async ( + tab: 'settings' | 'mappings' | 'stats' | 'edit_settings' + ) => { + const indexDetailsTabs = ['settings', 'mappings', 'stats', 'edit_settings']; + const { find, component } = testBed; + await act(async () => { + find('detailPanelTab').at(indexDetailsTabs.indexOf(tab)).simulate('click'); + }); + component.update(); + }; + + return { + ...testBed, + actions: { + selectIndexDetailsTab, + getIncludeHiddenIndicesToggleStatus, + clickIncludeHiddenIndicesToggle, + }, + }; +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts new file mode 100644 index 00000000000000..e5db5d547f1ab4 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -0,0 +1,105 @@ +/* + * 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. + */ + +import { act } from 'react-dom/test-utils'; + +import { API_BASE_PATH } from '../../../common/constants'; +import { setupEnvironment, nextTick } from '../helpers'; + +import { IndicesTestBed, setup } from './indices_tab.helpers'; + +describe('', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: IndicesTestBed; + + afterAll(() => { + server.restore(); + }); + + describe('on component mount', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + testBed = await setup(); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + test('sets the hash query param base on include hidden indices toggle', () => { + const { actions } = testBed; + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); + expect(window.location.hash.includes('includeHidden=true')).toBe(true); + actions.clickIncludeHiddenIndicesToggle(); + expect(window.location.hash.includes('includeHidden=true')).toBe(false); + // Note: this test modifies the shared location.hash state, we put it back the way it was + actions.clickIncludeHiddenIndicesToggle(); + expect(actions.getIncludeHiddenIndicesToggleStatus()).toBe(true); + expect(window.location.hash.includes('includeHidden=true')).toBe(true); + }); + }); + + describe('index detail panel with % character in index name', () => { + const indexName = 'test%'; + beforeEach(async () => { + const index = { + health: 'green', + status: 'open', + primary: 1, + replica: 1, + documents: 10000, + documents_deleted: 100, + size: '156kb', + primary_size: '156kb', + name: indexName, + }; + httpRequestsMockHelpers.setLoadIndicesResponse([index]); + + testBed = await setup(); + const { component, find } = testBed; + + component.update(); + + find('indexTableIndexNameLink').at(0).simulate('click'); + }); + + test('should encode indexName when loading settings in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('settings'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); + }); + + test('should encode indexName when loading mappings in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('mappings'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/mapping/${encodeURIComponent(indexName)}`); + }); + + test('should encode indexName when loading stats in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('stats'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/stats/${encodeURIComponent(indexName)}`); + }); + + test('should encode indexName when editing settings in detail panel', async () => { + const { actions } = testBed; + await actions.selectIndexDetailsTab('edit_settings'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); + }); + }); +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/constants.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts similarity index 100% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/constants.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/constants.ts diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_clone.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts similarity index 76% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_clone.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts index 36498b99ba1435..1a58cfa8fb55e8 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_clone.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts @@ -5,16 +5,16 @@ */ import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { TemplateClone } from '../../../public/application/sections/template_clone'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies } from '../helpers'; + import { formSetup } from './template_form.helpers'; import { TEMPLATE_NAME } from './constants'; -import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}clone_template/${TEMPLATE_NAME}`], - componentRoutePath: `${BASE_PATH}clone_template/:name`, + initialEntries: [`/clone_template/${TEMPLATE_NAME}`], + componentRoutePath: `/clone_template/:name`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx similarity index 88% rename from x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx index fa9d13d1ddd076..e0db9cd58ee238 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx @@ -3,21 +3,16 @@ * 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 from 'react'; import { act } from 'react-dom/test-utils'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { TemplateFormTestBed } from './helpers/template_form.helpers'; -import { getTemplate } from '../../test/fixtures'; -import { - TEMPLATE_NAME, - INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, - MAPPINGS, -} from './helpers/constants'; - -const { setup } = pageHelpers.templateClone; +import { getTemplate } from '../../../test/fixtures'; +import { setupEnvironment, nextTick } from '../helpers'; -jest.mock('ui/new_platform'); +import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, MAPPINGS } from './constants'; +import { setup } from './template_clone.helpers'; +import { TemplateFormTestBed } from './template_form.helpers'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_create.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts similarity index 77% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_create.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts index 14a44968a93c32..ab0a7b85676079 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_create.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts @@ -5,15 +5,15 @@ */ import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { TemplateCreate } from '../../../public/application/sections/template_create'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies } from '../helpers'; + import { formSetup, TestSubjects } from './template_form.helpers'; -import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}create_template`], - componentRoutePath: `${BASE_PATH}create_template`, + initialEntries: [`/create_template`], + componentRoutePath: `/create_template`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx similarity index 97% rename from x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index 8f464987418c03..95545b6c66f54b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -3,23 +3,22 @@ * 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 from 'react'; import { act } from 'react-dom/test-utils'; -import { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../common'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { TemplateFormTestBed } from './helpers/template_form.helpers'; +import { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../common'; +import { setupEnvironment, nextTick } from '../helpers'; + import { TEMPLATE_NAME, SETTINGS, MAPPINGS, ALIASES, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, -} from './helpers/constants'; - -const { setup } = pageHelpers.templateCreate; - -jest.mock('ui/new_platform'); +} from './constants'; +import { setup } from './template_create.helpers'; +import { TemplateFormTestBed } from './template_form.helpers'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_edit.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts similarity index 77% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_edit.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts index af5fa8b79ecad6..29ecd84e585ced 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_edit.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts @@ -5,16 +5,16 @@ */ import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { TemplateEdit } from '../../../public/application/sections/template_edit'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies } from '../helpers'; + import { formSetup, TestSubjects } from './template_form.helpers'; import { TEMPLATE_NAME } from './constants'; -import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}edit_template/${TEMPLATE_NAME}`], - componentRoutePath: `${BASE_PATH}edit_template/:name`, + initialEntries: [`/edit_template/${TEMPLATE_NAME}`], + componentRoutePath: `/edit_template/:name`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx similarity index 95% rename from x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index 0ed369e9b13f77..6e935a5263301b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -3,13 +3,16 @@ * 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 from 'react'; import { act } from 'react-dom/test-utils'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { TemplateFormTestBed } from './helpers/template_form.helpers'; -import * as fixtures from '../../test/fixtures'; -import { TEMPLATE_NAME, SETTINGS, ALIASES, MAPPINGS as DEFAULT_MAPPING } from './helpers/constants'; +import * as fixtures from '../../../test/fixtures'; +import { setupEnvironment, nextTick } from '../helpers'; + +import { TEMPLATE_NAME, SETTINGS, ALIASES, MAPPINGS as DEFAULT_MAPPING } from './constants'; +import { setup } from './template_edit.helpers'; +import { TemplateFormTestBed } from './template_form.helpers'; const UPDATED_INDEX_PATTERN = ['updatedIndexPattern']; const UPDATED_MAPPING_TEXT_FIELD_NAME = 'updated_text_datatype'; @@ -22,10 +25,6 @@ const MAPPING = { }, }; -const { setup } = pageHelpers.templateEdit; - -jest.mock('ui/new_platform'); - jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts similarity index 99% rename from x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts rename to x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index 21713428c43161..fdf837a914cf1f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -3,9 +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 { TestBed, SetupFunc, UnwrapPromise } from '../../../../../test_utils'; import { TemplateDeserialized } from '../../../common'; -import { nextTick } from './index'; +import { nextTick } from '../helpers'; interface MappingField { name: string; diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index ffd3cbb83c2ce7..8e8c2632a2372d 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -37,8 +37,6 @@ import { findTestSubject } from '@elastic/eui/lib/test'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { notificationServiceMock } from '../../../../../src/core/public/notifications/notifications_service.mock'; -jest.mock('ui/new_platform'); - const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); let server = null; diff --git a/x-pack/plugins/index_management/__mocks__/ace.js b/x-pack/plugins/index_management/__mocks__/ace.js deleted file mode 100644 index 40ce7026eee115..00000000000000 --- a/x-pack/plugins/index_management/__mocks__/ace.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 default { - edit: () => { - return { - navigateFileEnd() {}, - destroy() {}, - acequire() { - return { - setCompleters() {}, - }; - }, - setValue() {}, - setOptions() {}, - setTheme() {}, - setFontSize() {}, - setShowPrintMargin() {}, - getSession() { - return { - setUseWrapMode() {}, - setMode() {}, - setValue() {}, - on() {}, - }; - }, - renderer: { - setShowGutter() {}, - setScrollMargin() {}, - }, - setBehavioursEnabled() {}, - }; - }, - acequire() { - return { - setCompleters() {}, - }; - }, - setCompleters() { - return [{}]; - }, -}; diff --git a/x-pack/plugins/index_management/__mocks__/ui/documentation_links.js b/x-pack/plugins/index_management/__mocks__/ui/documentation_links.js deleted file mode 100644 index 0da03ba9b98ba8..00000000000000 --- a/x-pack/plugins/index_management/__mocks__/ui/documentation_links.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * 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 const settingsDocumentationLink = 'https://stuff.com/docs'; diff --git a/x-pack/plugins/index_management/__mocks__/ui/notify.js b/x-pack/plugins/index_management/__mocks__/ui/notify.js deleted file mode 100644 index 3d64a99232bc3f..00000000000000 --- a/x-pack/plugins/index_management/__mocks__/ui/notify.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 const toastNotifications = { - addInfo: () => {}, - addSuccess: () => {}, - addDanger: () => {}, - addWarning: () => {}, - addError: () => {}, -}; - -export function fatalError() {} diff --git a/x-pack/plugins/index_management/__jest__/lib/__snapshots__/flatten_object.test.js.snap b/x-pack/plugins/index_management/public/application/lib/__snapshots__/flatten_object.test.js.snap similarity index 100% rename from x-pack/plugins/index_management/__jest__/lib/__snapshots__/flatten_object.test.js.snap rename to x-pack/plugins/index_management/public/application/lib/__snapshots__/flatten_object.test.js.snap diff --git a/x-pack/plugins/index_management/__jest__/lib/flatten_object.test.js b/x-pack/plugins/index_management/public/application/lib/flatten_object.test.js similarity index 90% rename from x-pack/plugins/index_management/__jest__/lib/flatten_object.test.js rename to x-pack/plugins/index_management/public/application/lib/flatten_object.test.js index 0d6d5ee796627d..222d172d1ff862 100644 --- a/x-pack/plugins/index_management/__jest__/lib/flatten_object.test.js +++ b/x-pack/plugins/index_management/public/application/lib/flatten_object.test.js @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { flattenObject } from '../../public/application/lib/flatten_object'; +import { flattenObject } from './flatten_object'; + describe('flatten_object', () => { test('it flattens an object', () => { const obj = { @@ -17,6 +18,7 @@ describe('flatten_object', () => { }; expect(flattenObject(obj)).toMatchSnapshot(); }); + test('it flattens an object that contains an array in a field', () => { const obj = { foo: { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/index.ts index a6d4bfee29d55d..cc0e145909e624 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './index_list'; +export { IndexList } from './index_list';