From 0d78512d840d65b8d0f8eb0f606507f5de83d95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:34:19 +0000 Subject: [PATCH] [Index Management] Change the column Components in the Index templates table (#175823) ## Summary Fixes https://github.com/elastic/kibana/issues/175690 This PR changes the column Components to display a number of component templates instead of listing the names, since the text is truncated and in my opinion is not really readable. The full list of component templates can still be viewed in the index templates details flyout. The number of component templates is also a link that redirects to the Component templates table with a filter initialized in the search bar to view only those component templates that are used by a specific index template. ### Screenshots #### Index templates Before Screenshot 2024-01-29 at 16 41 55 After Screenshot 2024-01-29 at 16 40 40 #### Component templates Screenshot 2024-01-29 at 16 40 48 Summarize your PR. If it involves visual changes include a screenshot or gif. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../helpers/test_subjects.ts | 3 +- .../home/index_templates_tab.helpers.ts | 4 +-- .../home/index_templates_tab.test.ts | 29 +++++++++++++++---- .../common/types/templates.ts | 1 + .../component_template_list.test.ts | 18 ++++++++++++ .../component_template_list.helpers.ts | 20 +++++++++---- .../component_template_list.tsx | 3 ++ .../component_template_list_container.tsx | 11 ++++++- .../component_template_list/table.tsx | 4 +++ .../template_table/template_table.tsx | 22 ++++++++++---- .../public/application/services/routing.ts | 9 ++++++ .../test/fixtures/template.ts | 2 ++ 12 files changed, 107 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 6448b9b79d0a5..c68dcdfc53cec 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -113,4 +113,5 @@ export type TestSubjects = | 'createIndexMessage' | 'indicesSearch' | 'noIndicesMessage' - | 'clearIndicesSearch'; + | 'clearIndicesSearch' + | 'componentTemplatesLink'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts index a56d633217f96..6add533ef57fc 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts @@ -67,7 +67,7 @@ const createActions = (testBed: TestBed) => { component.update(); }; - const clickTemplateAction = ( + const clickTemplateAction = async ( templateName: TemplateDeserialized['name'], action: 'edit' | 'clone' | 'delete' ) => { @@ -76,7 +76,7 @@ const createActions = (testBed: TestBed) => { clickActionMenu(templateName); - act(() => { + await act(async () => { component.find('button.euiContextMenuItem').at(actions.indexOf(action)).simulate('click'); }); component.update(); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts index f052317513194..615b8df18f905 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -78,6 +78,26 @@ describe('Index Templates tab', () => { expect(exists('templateTable')).toBe(true); expect(exists('legacyTemplateTable')).toBe(false); }); + + test('Components column renders a link to Component templates', async () => { + httpRequestsMockHelpers.setLoadTemplatesResponse({ + templates: [ + fixtures.getComposableTemplate({ + name: 'Test', + composedOf: ['component1', 'component2'], + }), + ], + legacyTemplates: [], + }); + + await act(async () => { + testBed = await setup(httpSetup); + }); + const { exists, component } = testBed; + component.update(); + + expect(exists('componentTemplatesLink')).toBe(true); + }); }); describe('when there are index templates', () => { @@ -168,19 +188,18 @@ describe('Index Templates tab', () => { // Test composable table content tableCellsValues.forEach((row, i) => { const indexTemplate = templates[i]; - const { name, indexPatterns, ilmPolicy, composedOf, template } = indexTemplate; + const { name, indexPatterns, composedOf, template } = indexTemplate; const hasContent = !!template?.settings || !!template?.mappings || !!template?.aliases; - const ilmPolicyName = ilmPolicy && ilmPolicy.name ? ilmPolicy.name : ''; - const composedOfString = composedOf ? composedOf.join(',') : ''; + const composedOfCount = `${composedOf ? composedOf.length : 0}`; try { expect(removeWhiteSpaceOnArrayValues(row)).toEqual([ '', // Checkbox to select row name, indexPatterns.join(', '), - ilmPolicyName, - composedOfString, + composedOfCount, + '', // data stream column hasContent ? 'M S A' : 'None', // M S A -> Mappings Settings Aliases badges 'EditDelete', // Column of actions ]); diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts index 756d631202445..e2f530b4ad502 100644 --- a/x-pack/plugins/index_management/common/types/templates.ts +++ b/x-pack/plugins/index_management/common/types/templates.ts @@ -91,6 +91,7 @@ export interface TemplateListItem { ilmPolicy?: { name: string; }; + composedOf?: string[]; _kbnMeta: { type: TemplateType; hasDatastream: boolean; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts index c4595998bcd20..a843f9fe28597 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts @@ -173,6 +173,24 @@ describe('', () => { }); }); + describe('if filter is set, component templates are filtered', () => { + test('search value is set if url param is set', async () => { + const filter = 'usedBy=(test_index_template_1)'; + await act(async () => { + testBed = await setup(httpSetup, { filter }); + }); + + testBed.component.update(); + + const { table } = testBed; + const search = testBed.actions.getSearchValue(); + expect(search).toBe(filter); + + const { rows } = table.getMetaData('componentTemplatesTable'); + expect(rows.length).toBe(1); + }); + }); + describe('No component templates', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts index 1bd0152c86d40..8ebc905543a26 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts @@ -19,13 +19,14 @@ import { BASE_PATH } from '../../../../../../../common'; import { WithAppDependencies } from './setup_environment'; import { ComponentTemplateList } from '../../../component_template_list/component_template_list'; -const testBedConfig: AsyncTestBedConfig = { +const getTestBedConfig = (props?: any): AsyncTestBedConfig => ({ memoryRouter: { initialEntries: [`${BASE_PATH}component_templates`], componentRoutePath: `${BASE_PATH}component_templates`, }, doMountAsync: true, -}; + defaultProps: props, +}); export type ComponentTemplateListTestBed = TestBed & { actions: ReturnType; @@ -73,18 +74,26 @@ const createActions = (testBed: TestBed) => { deleteButton.simulate('click'); }; + const getSearchValue = () => { + return find('componentTemplatesSearch').prop('defaultValue'); + }; + return { clickReloadButton, clickComponentTemplateAt, clickDeleteActionAt, clickTableColumnSortButton, + getSearchValue, }; }; -export const setup = async (httpSetup: HttpSetup): Promise => { +export const setup = async ( + httpSetup: HttpSetup, + props?: any +): Promise => { const initTestBed = registerTestBed( WithAppDependencies(ComponentTemplateList, httpSetup), - testBedConfig + getTestBedConfig(props) ); const testBed = await initTestBed(); @@ -104,7 +113,8 @@ export type ComponentTemplateTestSubjects = | 'sectionLoading' | 'componentTemplatesLoadError' | 'deleteComponentTemplateButton' - | 'deprecatedComponentTemplateBadge' | 'reloadButton' + | 'componentTemplatesSearch' + | 'deprecatedComponentTemplateBadge' | 'componentTemplatesFiltersButton' | 'componentTemplates--deprecatedFilter'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx index fc309751bfe8d..65e369cc8c880 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx @@ -36,6 +36,7 @@ import { useRedirectPath } from '../../../hooks/redirect_path'; interface Props { componentTemplateName?: string; history: RouteComponentProps['history']; + filter?: string; } const { useGlobalFlyout } = GlobalFlyout; @@ -43,6 +44,7 @@ const { useGlobalFlyout } = GlobalFlyout; export const ComponentTemplateList: React.FunctionComponent = ({ componentTemplateName, history, + filter, }) => { const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = useGlobalFlyout(); @@ -183,6 +185,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ { const { executionContext } = useComponentTemplatesContext(); @@ -33,10 +35,17 @@ export const ComponentTemplateListContainer: React.FunctionComponent< page: 'indexManagementComponentTemplatesTab', }); + const urlParams = qs.parse(location.search); + const filter = urlParams.filter ?? ''; + return ( - + ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx index f4d9c55407fd9..7f6eb87566410 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx @@ -51,6 +51,7 @@ const deprecatedFilterLabel = i18n.translate( export interface Props { componentTemplates: ComponentTemplateListItem[]; + defaultFilter: string; onReloadClick: () => void; onDeleteClick: (componentTemplateName: string[]) => void; onEditClick: (componentTemplateName: string) => void; @@ -60,6 +61,7 @@ export interface Props { export const ComponentTable: FunctionComponent = ({ componentTemplates, + defaultFilter, onReloadClick, onDeleteClick, onEditClick, @@ -188,6 +190,7 @@ export const ComponentTable: FunctionComponent = ({ ], box: { incremental: true, + 'data-test-subj': 'componentTemplatesSearch', }, filters: [ { @@ -220,6 +223,7 @@ export const ComponentTable: FunctionComponent = ({ }, }, ], + defaultQuery: defaultFilter, }, pagination: { initialPageSize: 10, diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx index a0b4f07e61722..4a08a93c9a0c4 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx @@ -18,8 +18,8 @@ import { UseRequestResponse, reactRouterNavigate } from '../../../../../shared_i import { useServices } from '../../../../app_context'; import { TemplateDeleteModal } from '../../../../components'; import { TemplateContentIndicator } from '../../../../components/shared'; +import { getComponentTemplatesLink, getTemplateDetailsLink } from '../../../../services/routing'; import { TemplateTypeIndicator, TemplateDeprecatedBadge } from '../components'; -import { getTemplateDetailsLink } from '../../../../services/routing'; interface Props { templates: TemplateListItem[]; @@ -85,12 +85,24 @@ export const TemplateTable: React.FunctionComponent = ({ { field: 'composedOf', name: i18n.translate('xpack.idxMgmt.templateList.table.componentsColumnTitle', { - defaultMessage: 'Components', + defaultMessage: 'Component templates', }), + width: '100px', truncateText: true, - sortable: true, - width: '20%', - render: (composedOf: string[] = []) => {composedOf.join(', ')}, + sortable: (template) => { + return template.composedOf?.length; + }, + render: (composedOf: string[] = [], item: TemplateListItem) => + composedOf.length === 0 ? ( + 0 + ) : ( + + {composedOf.length} + + ), }, { name: i18n.translate('xpack.idxMgmt.templateList.table.dataStreamColumnTitle', { diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts index a2d4a03013556..07653d2591ffc 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -69,3 +69,12 @@ export const getIndexDetailsLink = ( } return link; }; + +export const getComponentTemplatesLink = (usedByTemplateName?: string) => { + let url = '/component_templates'; + if (usedByTemplateName) { + const filter = `usedBy=(${usedByTemplateName})`; + url = `${url}?filter=${encodeURIComponent(filter)}`; + } + return url; +}; diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts index 01f83e2d76ff5..f7db386095b07 100644 --- a/x-pack/plugins/index_management/test/fixtures/template.ts +++ b/x-pack/plugins/index_management/test/fixtures/template.ts @@ -23,6 +23,7 @@ export const getComposableTemplate = ({ isLegacy = false, type = 'default', allowAutoCreate = false, + composedOf = [], }: Partial< TemplateDeserialized & { isLegacy?: boolean; @@ -53,6 +54,7 @@ export const getComposableTemplate = ({ hasDatastream, isLegacy, }, + composedOf, }; return indexTemplate;