From 0afbc1fe73c41be218b7676de6c349cce9fb143a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 30 Jul 2021 17:03:51 +0200 Subject: [PATCH] [ILM] Added a flyout with linked index templates (#106734) (#107284) * [ILM] Server to use new in_use_by property returned by ES API * [ILM] Cleaning up the PR changes * [ILM] Fixed functional test * [ILM] Fixed 'modifiedDate' display in the table * [ILM] Fixed sorting test * [ILM] Removed a not needed function declaration * [ILM] Added index templates flyout to the policies list * [ILM] Added test for the index templates flyout * [ILM] Added an a11y test for the index templates flyout * Update x-pack/plugins/index_lifecycle_management/public/application/components/index_templates_flyout.tsx Co-authored-by: debadair * [ILM] Fixed jest test and made 0 index templates to not open the flyout * [ILM] Fixed a11y test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: debadair Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: debadair --- .../__snapshots__/policy_table.test.tsx.snap | 30 +++++++ .../edit_policy/init_test_bed.tsx | 4 +- .../__jest__/policy_table.test.tsx | 71 ++++++++++----- .../public/application/app.tsx | 45 +++------- .../components/index_templates_flyout.tsx | 86 +++++++++++++++++++ .../public/application/index.tsx | 25 +++--- .../searchable_snapshot_field.tsx | 4 +- .../shared_fields/snapshot_policies_field.tsx | 7 +- .../edit_policy/edit_policy.container.tsx | 17 +--- .../sections/edit_policy/edit_policy.tsx | 9 +- .../edit_policy/edit_policy_context.tsx | 2 - .../policy_table/components/table_content.tsx | 63 +++++++++++--- .../sections/policy_table/index.ts | 2 +- .../policy_table/policy_table.container.tsx | 20 +---- .../sections/policy_table/policy_table.tsx | 15 +--- .../public/application/services/sort_table.ts | 2 +- .../public/plugin.tsx | 5 +- .../public/types.ts | 4 + .../plugins/index_management/public/index.ts | 2 +- .../apps/index_lifecycle_management.ts | 30 +++++++ .../index_lifecycle_management/home_page.ts | 8 +- .../index_lifecycle_management_page.ts | 26 +----- 22 files changed, 296 insertions(+), 181 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/components/index_templates_flyout.tsx diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap index c3136a7ae671f..97d1c1757e85a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap @@ -106,6 +106,36 @@ exports[`policy table should show empty state when there are not any policies 1` `; +exports[`policy table should sort when linked index templates header is clicked 1`] = ` +Array [ + "testy1", + "testy3", + "testy5", + "testy7", + "testy9", + "testy11", + "testy13", + "testy15", + "testy17", + "testy19", +] +`; + +exports[`policy table should sort when linked index templates header is clicked 2`] = ` +Array [ + "testy0", + "testy104", + "testy102", + "testy100", + "testy98", + "testy96", + "testy94", + "testy92", + "testy90", + "testy88", +] +`; + exports[`policy table should sort when linked indices header is clicked 1`] = ` Array [ "testy1", diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx index 4f057e04c85d4..54d68edc7382f 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx @@ -23,9 +23,6 @@ const getTestBedConfig = (testBedConfigArgs?: Partial): TestBedCo initialEntries: [`/policies/edit/${POLICY_NAME}`], componentRoutePath: `/policies/edit/:policyName`, }, - defaultProps: { - getUrlForApp: () => {}, - }, ...testBedConfigArgs, }; }; @@ -38,6 +35,7 @@ const EditPolicyContainer = ({ appServicesContext, ...rest }: any) => { services={{ breadcrumbService, license: licensingMock.createLicense({ license: { type: 'enterprise' } }), + getUrlForApp: () => {}, ...appServicesContext, }} > diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx index 048776d7850ab..2fafbc6de98e4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx @@ -14,7 +14,6 @@ import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test'; import { fatalErrorsServiceMock, injectedMetadataServiceMock, - scopedHistoryMock, } from '../../../../src/core/public/mocks'; import { HttpService } from '../../../../src/core/public/http'; import { usageCollectionPluginMock } from '../../../../src/plugins/usage_collection/public/mocks'; @@ -23,6 +22,7 @@ import { PolicyFromES } from '../common/types'; import { PolicyTable } from '../public/application/sections/policy_table/policy_table'; import { init as initHttp } from '../public/application/services/http'; import { init as initUiMetric } from '../public/application/services/ui_metric'; +import { KibanaContextProvider } from '../public/shared_imports'; initHttp( new HttpService().setup({ @@ -36,12 +36,25 @@ initUiMetric(usageCollectionPluginMock.createSetupContract()); const testDate = '2020-07-21T14:16:58.666Z'; const testDateFormatted = moment(testDate).format('YYYY-MM-DD HH:mm:ss'); -const policies: PolicyFromES[] = []; -for (let i = 0; i < 105; i++) { +const testPolicy = { + version: 0, + modifiedDate: testDate, + indices: [`index1`], + indexTemplates: [`indexTemplate1`, `indexTemplate2`, `indexTemplate3`, `indexTemplate4`], + name: `testy0`, + policy: { + name: `testy0`, + phases: {}, + }, +}; + +const policies: PolicyFromES[] = [testPolicy]; +for (let i = 1; i < 105; i++) { policies.push({ version: i, - modifiedDate: i === 0 ? testDate : moment().subtract(i, 'days').toISOString(), + modifiedDate: moment().subtract(i, 'days').toISOString(), indices: i % 2 === 0 ? [`index${i}`] : [], + indexTemplates: i % 2 === 0 ? [`indexTemplate${i}`] : [], name: `testy${i}`, policy: { name: `testy${i}`, @@ -49,7 +62,14 @@ for (let i = 0; i < 105; i++) { }, }); } -jest.mock(''); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + createHref: jest.fn(), + }), +})); + let component: ReactElement; const snapshot = (rendered: string[]) => { @@ -88,24 +108,14 @@ const openContextMenu = (buttonIndex: number) => { describe('policy table', () => { beforeEach(() => { component = ( - + '' }}> + + ); }); test('should show empty state when there are not any policies', () => { - component = ( - - ); + component = ; const rendered = mountWithIntl(component); mountedSnapshot(rendered); }); @@ -147,6 +157,9 @@ describe('policy table', () => { test('should sort when linked indices header is clicked', () => { testSort('indices'); }); + test('should sort when linked index templates header is clicked', () => { + testSort('indexTemplates'); + }); test('should have proper actions in context menu when there are linked indices', () => { const rendered = openContextMenu(0); const buttons = rendered.find('button.euiContextMenuItem'); @@ -180,9 +193,21 @@ describe('policy table', () => { }); test('displays policy properties', () => { const rendered = mountWithIntl(component); - const firstRow = findTestSubject(rendered, 'policyTableRow').at(0).text(); - const version = 0; - const numberOfIndices = 1; - expect(firstRow).toBe(`testy0${numberOfIndices}${version}${testDateFormatted}Actions`); + const firstRow = findTestSubject(rendered, 'policyTableRow-testy0').text(); + const numberOfIndices = testPolicy.indices.length; + const numberOfIndexTemplates = testPolicy.indexTemplates.length; + expect(firstRow).toBe( + `testy0${numberOfIndices}${numberOfIndexTemplates}${testPolicy.version}${testDateFormatted}Actions` + ); + }); + test('opens a flyout with index templates', () => { + const rendered = mountWithIntl(component); + const indexTemplatesButton = findTestSubject(rendered, 'viewIndexTemplates').at(0); + indexTemplatesButton.simulate('click'); + rendered.update(); + const flyoutTitle = findTestSubject(rendered, 'indexTemplatesFlyoutHeader').text(); + expect(flyoutTitle).toContain('testy0'); + const indexTemplatesLinks = findTestSubject(rendered, 'indexTemplateLink'); + expect(indexTemplatesLinks.length).toBe(testPolicy.indexTemplates.length); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index 3335720ab3065..4bc4e62ac52b6 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -7,50 +7,25 @@ import React, { useEffect } from 'react'; import { Router, Switch, Route, Redirect } from 'react-router-dom'; -import { ScopedHistory, ApplicationStart } from 'kibana/public'; +import { ScopedHistory } from 'kibana/public'; import { METRIC_TYPE } from '@kbn/analytics'; -import { UIM_APP_LOAD } from './constants/ui_metric'; +import { UIM_APP_LOAD } from './constants'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; import { ROUTES } from './services/navigation'; -export const AppWithRouter = ({ - history, - navigateToApp, - getUrlForApp, -}: { - history: ScopedHistory; - navigateToApp: ApplicationStart['navigateToApp']; - getUrlForApp: ApplicationStart['getUrlForApp']; -}) => ( - - - -); - -export const App = ({ - navigateToApp, - getUrlForApp, -}: { - navigateToApp: ApplicationStart['navigateToApp']; - getUrlForApp: ApplicationStart['getUrlForApp']; -}) => { +export const App = ({ history }: { history: ScopedHistory }) => { useEffect(() => trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD), []); return ( - - - } - /> - } - /> - + + + + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/components/index_templates_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/components/index_templates_flyout.tsx new file mode 100644 index 0000000000000..abfda9fce4ea4 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/components/index_templates_flyout.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonEmpty, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiInMemoryTable, + EuiLink, + EuiTitle, +} from '@elastic/eui'; +import { PolicyFromES } from '../../../common/types'; +import { useKibana } from '../../shared_imports'; +import { getTemplateDetailsLink } from '../../../../index_management/public/'; + +interface Props { + policy: PolicyFromES; + close: () => void; +} +export const IndexTemplatesFlyout: FunctionComponent = ({ policy, close }) => { + const { + services: { getUrlForApp }, + } = useKibana(); + const getUrlForIndexTemplate = (name: string) => { + return getUrlForApp('management', { + path: `data/index_management${getTemplateDetailsLink(name)}`, + }); + }; + return ( + + + +

+ +

+
+
+ + { + return ( + + {value} + + ); + }, + }, + ]} + /> + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx index ed0a4a83b06db..e158be26dfcc7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -14,30 +14,31 @@ import { ILicense } from '../../../licensing/public'; import { KibanaContextProvider } from '../shared_imports'; -import { AppWithRouter } from './app'; +import { App } from './app'; import { BreadcrumbService } from './services/breadcrumbs'; +import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public'; export const renderApp = ( element: Element, I18nContext: I18nStart['Context'], history: ScopedHistory, - navigateToApp: ApplicationStart['navigateToApp'], - getUrlForApp: ApplicationStart['getUrlForApp'], + application: ApplicationStart, breadcrumbService: BreadcrumbService, license: ILicense, cloud?: CloudSetup ): UnmountCallback => { + const { navigateToApp, getUrlForApp } = application; render( - - - - - , + + + + + + + , element ); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx index 7f99b10c776f7..44459debc8f4d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx @@ -85,9 +85,9 @@ export const SearchableSnapshotField: FunctionComponent = ({ canBeDisabled = true, }) => { const { - services: { cloud }, + services: { cloud, getUrlForApp }, } = useKibana(); - const { getUrlForApp, policy, license, isNewPolicy } = useEditPolicyContext(); + const { policy, license, isNewPolicy } = useEditPolicyContext(); const { isUsingSearchableSnapshotInHotPhase } = useConfiguration(); const searchableSnapshotRepoPath = `phases.${phase}.actions.searchable_snapshot.snapshot_repository`; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx index 21dd083ccf7c5..720d39695cf0e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx @@ -18,10 +18,9 @@ import { EuiSpacer, } from '@elastic/eui'; -import { ComboBoxField, useFormData } from '../../../../../../shared_imports'; +import { ComboBoxField, useFormData, useKibana } from '../../../../../../shared_imports'; import { useLoadSnapshotPolicies } from '../../../../../services/api'; -import { useEditPolicyContext } from '../../../edit_policy_context'; import { UseField } from '../../../form'; import { FieldLoadingError, LearnMoreLink, OptionalLabel } from '../../'; @@ -29,7 +28,9 @@ import { FieldLoadingError, LearnMoreLink, OptionalLabel } from '../../'; const waitForSnapshotFormField = 'phases.delete.actions.wait_for_snapshot.policy'; export const SnapshotPoliciesField: React.FunctionComponent = () => { - const { getUrlForApp } = useEditPolicyContext(); + const { + services: { getUrlForApp }, + } = useKibana(); const { error, isLoading, data, resendRequest } = useLoadSnapshotPolicies(); const [formData] = useFormData({ watch: waitForSnapshotFormField, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx index da63bc87424d6..90a3b0f14459d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx @@ -24,22 +24,10 @@ interface RouterProps { policyName: string; } -interface Props { - getUrlForApp: ( - appId: string, - options?: { - path?: string; - absolute?: boolean; - } - ) => string; -} - -export const EditPolicy: React.FunctionComponent> = ({ +export const EditPolicy: React.FunctionComponent> = ({ match: { params: { policyName }, }, - getUrlForApp, - history, }) => { const { services: { breadcrumbService, license }, @@ -105,13 +93,12 @@ export const EditPolicy: React.FunctionComponent license.hasAtLeast(MIN_SEARCHABLE_SNAPSHOT_LICENSE), }, }} > - + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index 172e8259b87af..371be58920d07 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { get } from 'lodash'; -import { RouteComponentProps } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { EuiButton, @@ -50,13 +50,9 @@ import { import { useEditPolicyContext } from './edit_policy_context'; import { FormInternal } from './types'; -export interface Props { - history: RouteComponentProps['history']; -} - const policyNamePath = 'name'; -export const EditPolicy: React.FunctionComponent = ({ history }) => { +export const EditPolicy: React.FunctionComponent = () => { useEffect(() => { window.scrollTo(0, 0); }, []); @@ -119,6 +115,7 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { [originalPolicyName, existingPolicies, saveAsNew] ); + const history = useHistory(); const backToPolicyList = () => { history.push('/policies'); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx index 45f0fe8544c98..9414f27c72ea9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx @@ -6,7 +6,6 @@ */ import React, { createContext, ReactChild, useContext } from 'react'; -import { ApplicationStart } from 'kibana/public'; import { PolicyFromES, SerializedPolicy } from '../../../../common/types'; @@ -14,7 +13,6 @@ export interface EditPolicyContextValue { isNewPolicy: boolean; policy: SerializedPolicy; existingPolicies: PolicyFromES[]; - getUrlForApp: ApplicationStart['getUrlForApp']; license: { canUseSearchableSnapshot: () => boolean; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx index 3d81bd03c3655..94d815b7ef932 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx @@ -20,7 +20,6 @@ import { EuiTablePagination, EuiTableRow, EuiTableRowCell, - EuiText, Pager, EuiContextMenuPanelDescriptor, } from '@elastic/eui'; @@ -30,12 +29,12 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import moment from 'moment'; -import { ApplicationStart } from 'kibana/public'; import { METRIC_TYPE } from '@kbn/analytics'; -import { RouteComponentProps } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { getIndexListUri } from '../../../../../../index_management/public'; import { PolicyFromES } from '../../../../../common/types'; +import { useKibana } from '../../../../shared_imports'; import { getPolicyEditPath } from '../../../services/navigation'; import { sortTable } from '../../../services'; import { trackUiMetric } from '../../../services/ui_metric'; @@ -44,6 +43,7 @@ import { UIM_EDIT_CLICK } from '../../../constants'; import { TableColumn } from '../index'; import { AddPolicyToTemplateConfirmModal } from './add_policy_to_template_confirm_modal'; import { ConfirmDelete } from './confirm_delete'; +import { IndexTemplatesFlyout } from '../../../components/index_templates_flyout'; const COLUMNS: Array<[TableColumn, { label: string; width: number }]> = [ [ @@ -64,6 +64,18 @@ const COLUMNS: Array<[TableColumn, { label: string; width: number }]> = [ width: 120, }, ], + [ + 'indexTemplates', + { + label: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.headers.linkedIndexTemplatesHeader', + { + defaultMessage: 'Linked index templates', + } + ), + width: 160, + }, + ], [ 'version', { @@ -87,18 +99,14 @@ const COLUMNS: Array<[TableColumn, { label: string; width: number }]> = [ interface Props { policies: PolicyFromES[]; totalNumber: number; - navigateToApp: ApplicationStart['navigateToApp']; setConfirmModal: (modal: ReactElement | null) => void; handleDelete: () => void; - history: RouteComponentProps['history']; } export const TableContent: React.FunctionComponent = ({ policies, totalNumber, - navigateToApp, setConfirmModal, handleDelete, - history, }) => { const [popoverPolicy, setPopoverPolicy] = useState(); const [sort, setSort] = useState<{ sortField: TableColumn; isSortAscending: boolean }>({ @@ -107,6 +115,10 @@ export const TableContent: React.FunctionComponent = ({ }); const [pageSize, setPageSize] = useState(10); const [currentPage, setCurrentPage] = useState(0); + const history = useHistory(); + const { + services: { navigateToApp }, + } = useKibana(); let sortedPolicies = sortTable(policies, sort.sortField, sort.isSortAscending); const pager = new Pager(totalNumber, pageSize, currentPage); @@ -224,7 +236,11 @@ export const TableContent: React.FunctionComponent = ({ return [panelTree]; }; - const renderRowCell = (fieldName: string, value: string | number | string[]): ReactNode => { + const renderRowCell = ( + fieldName: string, + value: string | number | string[], + policy?: PolicyFromES + ): ReactNode => { if (fieldName === 'name') { return ( = ({ ); } else if (fieldName === 'indices') { - return ( - - {value ? (value as string[]).length : '0'} - + return value ? (value as string[]).length : '0'; + } else if (fieldName === 'indexTemplates' && policy) { + return value && (value as string[]).length > 0 ? ( + setConfirmModal(renderIndexTemplatesFlyout(policy))} + > + {(value as string[]).length} + + ) : ( + '0' ); } else if (fieldName === 'modifiedDate' && value) { return moment(value).format('YYYY-MM-DD HH:mm:ss'); @@ -276,7 +300,7 @@ export const TableContent: React.FunctionComponent = ({ className={'policyTable__content--' + fieldName} width={width} > - {renderRowCell(fieldName, value)} + {renderRowCell(fieldName, value, policy)} ); } @@ -319,7 +343,7 @@ export const TableContent: React.FunctionComponent = ({ const rows = sortedPolicies.map((policy) => { const { name } = policy; return ( - + {renderRowCells(policy)} ); @@ -343,6 +367,17 @@ export const TableContent: React.FunctionComponent = ({ ); }; + const renderIndexTemplatesFlyout = (policy: PolicyFromES): ReactElement => { + return ( + { + setConfirmModal(null); + }} + /> + ); + }; + const renderPager = (): ReactNode => { return ( ; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx index ad8d1ed87f5f2..376832bc99061 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx @@ -6,22 +6,13 @@ */ import React, { useEffect } from 'react'; -import { ApplicationStart } from 'kibana/public'; -import { RouteComponentProps } from 'react-router-dom'; import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { PolicyTable as PresentationComponent } from './policy_table'; import { useKibana } from '../../../shared_imports'; import { useLoadPoliciesList } from '../../services/api'; -interface Props { - navigateToApp: ApplicationStart['navigateToApp']; -} - -export const PolicyTable: React.FunctionComponent = ({ - navigateToApp, - history, -}) => { +export const PolicyTable: React.FunctionComponent = () => { const { services: { breadcrumbService }, } = useKibana(); @@ -77,12 +68,5 @@ export const PolicyTable: React.FunctionComponent = ); } - return ( - - ); + return ; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx index 30a2b9e68d69d..28cc2b17dcbff 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx @@ -19,8 +19,7 @@ import { EuiPageHeader, EuiPageContent, } from '@elastic/eui'; -import { ApplicationStart } from 'kibana/public'; -import { RouteComponentProps } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { PolicyFromES } from '../../../../common/types'; import { filterItems } from '../../services'; @@ -29,19 +28,13 @@ import { getPolicyCreatePath } from '../../services/navigation'; interface Props { policies: PolicyFromES[]; - history: RouteComponentProps['history']; - navigateToApp: ApplicationStart['navigateToApp']; updatePolicies: () => void; } -export const PolicyTable: React.FunctionComponent = ({ - policies, - history, - navigateToApp, - updatePolicies, -}) => { +export const PolicyTable: React.FunctionComponent = ({ policies, updatePolicies }) => { const [confirmModal, setConfirmModal] = useState(); const [filter, setFilter] = useState(''); + const history = useHistory(); const createPolicyButton = ( = ({ { updatePolicies(); setConfirmModal(null); }} - history={history} /> ); } else { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts index a1e43d3b0d745..8c381f1e90daf 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts @@ -15,7 +15,7 @@ export const sortTable = ( isSortAscending: boolean ): PolicyFromES[] => { let sorter; - if (sortField === 'indices') { + if (sortField === 'indices' || sortField === 'indexTemplates') { sorter = (item: PolicyFromES) => (item[sortField] || []).length; } else { sorter = (item: PolicyFromES) => item[sortField]; diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 163fe2b3d9b5c..8381122b49b5e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -55,7 +55,7 @@ export class IndexLifecycleManagementPlugin chrome: { docTitle }, i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, - application: { navigateToApp, getUrlForApp }, + application, } = coreStart; const license = await licensing.license$.pipe(first()).toPromise(); @@ -74,8 +74,7 @@ export class IndexLifecycleManagementPlugin element, I18nContext, history, - navigateToApp, - getUrlForApp, + application, this.breadcrumbService, license, cloud diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts index adfca9ad41b26..c54f5620a2859 100644 --- a/x-pack/plugins/index_lifecycle_management/public/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/types.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import { ApplicationStart } from 'kibana/public'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; @@ -37,4 +39,6 @@ export interface AppServicesContext { breadcrumbService: BreadcrumbService; license: ILicense; cloud?: CloudSetup; + navigateToApp: ApplicationStart['navigateToApp']; + getUrlForApp: ApplicationStart['getUrlForApp']; } diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index 3df5fef4b02fc..10feabf0a9d0f 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -15,6 +15,6 @@ export const plugin = () => { export { IndexManagementPluginSetup } from './types'; -export { getIndexListUri } from './application/services/routing'; +export { getIndexListUri, getTemplateDetailsLink } from './application/services/routing'; export type { Index } from '../common'; diff --git a/x-pack/test/accessibility/apps/index_lifecycle_management.ts b/x-pack/test/accessibility/apps/index_lifecycle_management.ts index 0daeda5ac61f3..dcd690607c64a 100644 --- a/x-pack/test/accessibility/apps/index_lifecycle_management.ts +++ b/x-pack/test/accessibility/apps/index_lifecycle_management.ts @@ -34,6 +34,8 @@ const POLICY_ALL_PHASES = { }, }; +const indexTemplateName = 'ilm-a11y-test-template'; + export default function ({ getService, getPageObjects }: FtrProviderContext) { const { common, indexLifecycleManagement } = getPageObjects([ 'common', @@ -65,11 +67,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Index Lifecycle Management', async () => { before(async () => { await esClient.ilm.putLifecycle({ policy: POLICY_NAME, body: POLICY_ALL_PHASES }); + await esClient.indices.putIndexTemplate({ + name: indexTemplateName, + body: { + template: { + settings: { + lifecycle: { + name: POLICY_NAME, + }, + }, + }, + index_patterns: ['test*'], + }, + }); }); after(async () => { // @ts-expect-error @elastic/elasticsearch DeleteSnapshotLifecycleRequest.policy_id is required await esClient.ilm.deleteLifecycle({ policy: POLICY_NAME }); + await esClient.indices.deleteIndexTemplate({ name: indexTemplateName }); }); beforeEach(async () => { @@ -165,5 +181,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); + + it('Index templates flyout', async () => { + const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); + const actionsButton = await policyRow.findByTestSubject('viewIndexTemplates'); + + await actionsButton.click(); + + const flyoutTitleSelector = 'indexTemplatesFlyoutHeader'; + await retry.waitFor('Index templates flyout', async () => { + return testSubjects.isDisplayed(flyoutTitleSelector); + }); + + await a11y.testAppSnapshot(); + }); }); } diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts index bd70a50724a9c..c51e2968baee0 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts @@ -52,13 +52,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.indexLifecycleManagement.increasePolicyListPageSize(); - const allPolicies = await pageObjects.indexLifecycleManagement.getPolicyList(); + const createdPolicy = await pageObjects.indexLifecycleManagement.getPolicyRow(policyName); - const filteredPolicies = allPolicies.filter(function (policy) { - return policy.name === policyName; - }); - - expect(filteredPolicies.length).to.be(1); + expect(createdPolicy.length).to.be(1); }); }); }; diff --git a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts index 1d12da202e051..6706579d5c399 100644 --- a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts +++ b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { map as mapAsync } from 'bluebird'; import { FtrProviderContext } from '../ftr_provider_context'; interface Policy { @@ -83,29 +82,8 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider await testSubjects.click(`tablePagination-100-rows`); }, - async getPolicyList() { - const policies = await testSubjects.findAll('policyTableRow'); - return mapAsync(policies, async (policy) => { - const policyNameElement = await policy.findByTestSubject('policyTableCell-name'); - const policyLinkedIndicesElement = await policy.findByTestSubject( - 'policyTableCell-indices' - ); - const policyVersionElement = await policy.findByTestSubject('policyTableCell-version'); - const policyModifiedDateElement = await policy.findByTestSubject( - 'policyTableCell-modifiedDate' - ); - const policyActionsButtonElement = await policy.findByTestSubject( - 'policyActionsContextMenuButton' - ); - - return { - name: await policyNameElement.getVisibleText(), - indices: await policyLinkedIndicesElement.getVisibleText(), - version: await policyVersionElement.getVisibleText(), - modifiedDate: await policyModifiedDateElement.getVisibleText(), - actionsButton: policyActionsButtonElement, - }; - }); + async getPolicyRow(name: string) { + return await testSubjects.findAll(`policyTableRow-${name}`); }, }; }