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;