diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_flyout.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_flyout.test.tsx.snap new file mode 100644 index 0000000000000..91a77cb2096f2 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_flyout.test.tsx.snap @@ -0,0 +1,1027 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`View policy flyout shows all phases 1`] = ` +
+ +
+
+
+
+ +
+
+
+ + + +`; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/mocks.ts b/x-pack/plugins/index_lifecycle_management/__jest__/mocks.ts new file mode 100644 index 0000000000000..69363fdd660cf --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/mocks.ts @@ -0,0 +1,97 @@ +/* + * 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 type { PolicyFromES } from '../common/types'; + +export const policyAllPhases: PolicyFromES = { + name: 'test', + modifiedDate: '2024-08-12T12:17:06.271Z', + version: 1, + policy: { + name: 'test', + phases: { + hot: { + actions: { + rollover: { + max_age: '30d', + max_primary_shard_size: '50gb', + max_primary_shard_docs: 25, + max_docs: 235, + max_size: '2gb', + }, + set_priority: { + priority: 100, + }, + forcemerge: { + max_num_segments: 3, + index_codec: 'best_compression', + }, + shrink: { + number_of_shards: 1, + }, + readonly: {}, + }, + min_age: '0ms', + }, + warm: { + min_age: '3d', + actions: { + set_priority: { + priority: 50, + }, + shrink: { + max_primary_shard_size: '4gb', + }, + forcemerge: { + max_num_segments: 44, + index_codec: 'best_compression', + }, + allocate: { + number_of_replicas: 3, + }, + downsample: { + fixed_interval: '1d', + }, + }, + }, + cold: { + min_age: '55d', + actions: { + searchable_snapshot: { + snapshot_repository: 'found-snapshots', + }, + set_priority: { + priority: 0, + }, + allocate: { + number_of_replicas: 3, + }, + downsample: { + fixed_interval: '4d', + }, + }, + }, + frozen: { + min_age: '555d', + actions: { + searchable_snapshot: { + snapshot_repository: 'found-snapshots', + }, + }, + }, + delete: { + min_age: '7365d', + actions: { + wait_for_snapshot: { + policy: 'cloud-snapshot-policy', + }, + delete: {}, + }, + }, + }, + }, +}; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/policy_flyout.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/policy_flyout.test.tsx new file mode 100644 index 0000000000000..75b26d98ddc9f --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/policy_flyout.test.tsx @@ -0,0 +1,57 @@ +/* + * 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, { ReactElement } from 'react'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test'; + +import { docLinksServiceMock } from '@kbn/core/public/mocks'; + +import type { PolicyFromES } from '../common/types'; +import { KibanaContextProvider } from '../public/shared_imports'; +import { PolicyListContextProvider } from '../public/application/sections/policy_list/policy_list_context'; +import { ViewPolicyFlyout } from '../public/application/sections/policy_list/policy_flyout'; +import * as readOnlyHook from '../public/application/lib/use_is_read_only'; +import { policyAllPhases } from './mocks'; + +let component: ReactElement; +const TestComponent = ({ policy }: { policy: PolicyFromES }) => { + return ( + '', docLinks: docLinksServiceMock.createStartContract() }} + > + + + + + ); +}; + +describe('View policy flyout', () => { + beforeAll(() => { + jest.spyOn(readOnlyHook, 'useIsReadOnly').mockReturnValue(false); + component = ; + }); + it('shows all phases', () => { + const rendered = mountWithIntl(component); + expect(takeMountedSnapshot(rendered)).toMatchSnapshot(); + }); + + it('renders manage button', () => { + const rendered = mountWithIntl(component); + const button = findTestSubject(rendered, 'managePolicyButton'); + expect(button.exists()).toBeTruthy(); + }); + + it(`doesn't render manage button in read only view`, () => { + jest.spyOn(readOnlyHook, 'useIsReadOnly').mockReturnValue(true); + component = ; + const rendered = mountWithIntl(component); + const button = findTestSubject(rendered, 'managePolicyButton'); + expect(button.exists()).toBeFalsy(); + }); +}); 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 19c0078c7eb6a..fe3b225499c33 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 @@ -21,6 +21,7 @@ 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'; import { PolicyListContextProvider } from '../public/application/sections/policy_list/policy_list_context'; +import * as readOnlyHook from '../public/application/lib/use_is_read_only'; initHttp(httpServiceMock.createSetupContract()); initUiMetric(usageCollectionPluginMock.createSetupContract()); @@ -72,9 +73,16 @@ jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: () => ({ createHref: jest.fn(), + location: { + search: '', + }, }), })); - +const mockReactRouterNavigate = jest.fn(); +jest.mock('@kbn/kibana-react-plugin/public', () => ({ + ...jest.requireActual('@kbn/kibana-react-plugin/public'), + reactRouterNavigate: () => mockReactRouterNavigate(), +})); let component: ReactElement; const snapshot = (rendered: string[]) => { @@ -129,6 +137,7 @@ const TestComponent = ({ testPolicies }: { testPolicies: PolicyFromES[] }) => { }; describe('policy table', () => { beforeEach(() => { + jest.spyOn(readOnlyHook, 'useIsReadOnly').mockReturnValue(false); component = ; window.localStorage.removeItem('ILM_SHOW_MANAGED_POLICIES_BY_DEFAULT'); }); @@ -296,7 +305,9 @@ describe('policy table', () => { test('add index template modal shows when add policy to index template button is pressed', () => { const rendered = mountWithIntl(component); const policyRow = findTestSubject(rendered, `policyTableRow-${testPolicy.name}`); - const addPolicyToTemplateButton = findTestSubject(policyRow, 'addPolicyToTemplate'); + const actionsButton = findTestSubject(policyRow, 'euiCollapsedItemActionsButton'); + actionsButton.simulate('click'); + const addPolicyToTemplateButton = findTestSubject(rendered, 'addPolicyToTemplate'); addPolicyToTemplateButton.simulate('click'); rendered.update(); expect(findTestSubject(rendered, 'addPolicyToTemplateModal').exists()).toBeTruthy(); @@ -312,6 +323,10 @@ describe('policy table', () => { expect(policyIndices).toBe(`${testPolicy.indices.length}`); const policyModifiedDate = findTestSubject(firstRow, 'policy-modifiedDate').text(); expect(policyModifiedDate).toBe(`${testDateFormatted}`); + + const cells = firstRow.find('td'); + // columns are name, linked index templates, linked indices, modified date, actions + expect(cells.length).toBe(5); }); test('opens a flyout with index templates', () => { const rendered = mountWithIntl(component); @@ -323,4 +338,25 @@ describe('policy table', () => { const indexTemplatesLinks = findTestSubject(rendered, 'indexTemplateLink'); expect(indexTemplatesLinks.length).toBe(testPolicy.indexTemplates.length); }); + test('opens a flyout to view policy by calling reactRouterNavigate', async () => { + const rendered = mountWithIntl(component); + const policyNameLink = findTestSubject(rendered, 'policyTablePolicyNameLink').at(0); + policyNameLink.simulate('click'); + rendered.update(); + expect(mockReactRouterNavigate).toHaveBeenCalled(); + }); + + describe('read only view', () => { + beforeEach(() => { + jest.spyOn(readOnlyHook, 'useIsReadOnly').mockReturnValue(true); + component = ; + }); + it(`doesn't show actions column in the table`, () => { + const rendered = mountWithIntl(component); + const policyRow = findTestSubject(rendered, `policyTableRow-testy0`); + const cells = policyRow.find('td'); + // columns are name, linked index templates, linked indices, modified date + expect(cells.length).toBe(4); + }); + }); }); diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 236dfa85118eb..7ebb789f62e54 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -60,8 +60,6 @@ export interface SerializedActionWithAllocation { migrate?: MigrateAction; } -export type SearchableSnapshotStorage = 'full_copy' | 'shared_cache'; - export interface SearchableSnapshotAction { snapshot_repository: string; /** @@ -69,12 +67,6 @@ export interface SearchableSnapshotAction { * not suit the vast majority of cases. */ force_merge_index?: boolean; - /** - * This configuration lets the user create full or partial searchable snapshots. - * Full searchable snapshots store primary data locally and store replica data in the snapshot. - * Partial searchable snapshots store no data locally. - */ - storage?: SearchableSnapshotStorage; } export interface RolloverAction { @@ -96,9 +88,7 @@ export interface SerializedHotPhase extends SerializedPhase { shrink?: ShrinkAction; downsample?: DownsampleAction; - set_priority?: { - priority: number | null; - }; + set_priority?: SetPriorityAction; /** * Only available on enterprise license */ @@ -113,9 +103,7 @@ export interface SerializedWarmPhase extends SerializedPhase { forcemerge?: ForcemergeAction; readonly?: {}; downsample?: DownsampleAction; - set_priority?: { - priority: number | null; - }; + set_priority?: SetPriorityAction; migrate?: MigrateAction; }; } @@ -126,9 +114,7 @@ export interface SerializedColdPhase extends SerializedPhase { readonly?: {}; downsample?: DownsampleAction; allocate?: AllocateAction; - set_priority?: { - priority: number | null; - }; + set_priority?: SetPriorityAction; migrate?: MigrateAction; /** * Only available on enterprise license @@ -139,11 +125,6 @@ export interface SerializedColdPhase extends SerializedPhase { export interface SerializedFrozenPhase extends SerializedPhase { actions: { - allocate?: AllocateAction; - set_priority?: { - priority: number | null; - }; - migrate?: MigrateAction; /** * Only available on enterprise license */ @@ -187,11 +168,8 @@ export interface DownsampleAction { fixed_interval: string; } -export interface LegacyPolicy { - name: string; - phases: { - delete: DeletePhase; - }; +export interface SetPriorityAction { + priority: number | null; } export interface CommonPhaseSettings { @@ -203,44 +181,6 @@ export interface PhaseWithMinAge { selectedMinimumAgeUnits: string; } -export interface PhaseWithIndexPriority { - phaseIndexPriority: string; -} - -export interface PhaseWithForcemergeAction { - forceMergeEnabled: boolean; - selectedForceMergeSegments: string; - bestCompressionEnabled: boolean; -} - export interface DeletePhase extends CommonPhaseSettings, PhaseWithMinAge { waitForSnapshotPolicy: string; } - -export interface IndexLifecyclePolicy { - index: string; - managed: boolean; - action?: string; - action_time_millis?: number; - age?: string; - failed_step?: string; - failed_step_retry_count?: number; - is_auto_retryable_error?: boolean; - lifecycle_date_millis?: number; - phase?: string; - phase_execution?: { - policy: string; - modified_date_in_millis: number; - version: number; - phase_definition: SerializedPhase; - }; - phase_time_millis?: number; - policy?: string; - step?: string; - step_info?: { - reason?: string; - type?: string; - message?: string; - }; - step_time_millis?: number; -} diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts index 66810ebb1e546..5f148bc89c551 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.helpers.ts @@ -22,8 +22,9 @@ const getTestBedConfig = (initialEntries: string[]): TestBedConfig => ({ export interface AppTestBed extends TestBed { actions: { - clickPolicyNameLink: () => void; - clickCreatePolicyButton: () => void; + clickPolicyNameLink: () => Promise; + clickCreatePolicyButton: () => Promise; + clickEditPolicyButton: () => Promise; }; } @@ -53,9 +54,17 @@ export const setup = async ( component.update(); }; + const clickEditPolicyButton = async () => { + const { component, find } = testBed; + await act(async () => { + find('editPolicy').simulate('click', { button: 0 }); + }); + component.update(); + }; + return { ...testBed, - actions: { clickPolicyNameLink, clickCreatePolicyButton }, + actions: { clickPolicyNameLink, clickCreatePolicyButton, clickEditPolicyButton }, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.test.ts index 1aaec089724b6..c2db9a4fc571b 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/app/app.test.ts @@ -7,6 +7,7 @@ import { act } from 'react-dom/test-utils'; +import * as hooks from '../../public/application/lib/use_is_read_only'; import { getDefaultHotPhasePolicy } from '../edit_policy/constants'; import { setupEnvironment } from '../helpers'; @@ -41,6 +42,7 @@ jest.mock('@elastic/eui', () => { describe('', () => { let testBed: AppTestBed; const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); + jest.spyOn(hooks, 'useIsReadOnly').mockReturnValue(false); describe('new policy creation', () => { test('when there are no policies', async () => { @@ -92,7 +94,7 @@ describe('', () => { await actions.clickPolicyNameLink(); component.update(); - expect(testBed.find('policyTitle').text()).toBe(`${editPolicyTitle} ${SPECIAL_CHARS_NAME}`); + expect(testBed.find('policyFlyoutTitle').text()).toBe(SPECIAL_CHARS_NAME); }); test('loading edit policy page url works', async () => { @@ -166,9 +168,7 @@ describe('', () => { await actions.clickPolicyNameLink(); component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `${editPolicyTitle} ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` - ); + expect(testBed.find('policyFlyoutTitle').text()).toBe(PERCENT_SIGN_WITH_OTHER_CHARS_NAME); }); test("loading edit policy page url doesn't work", async () => { @@ -221,9 +221,7 @@ describe('', () => { await actions.clickPolicyNameLink(); component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `${editPolicyTitle} ${PERCENT_SIGN_25_SEQUENCE}` - ); + expect(testBed.find('policyFlyoutTitle').text()).toBe(PERCENT_SIGN_25_SEQUENCE); }); test("loading edit policy page url doesn't work", async () => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/components/index.ts new file mode 100644 index 0000000000000..31a46d57abc66 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/components/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { IndexTemplatesFlyout } from './index_templates_flyout'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index 293e232e349df..2c025761fd940 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -11,7 +11,6 @@ export const defaultIndexPriority = { hot: '100', warm: '50', cold: '0', - frozen: '0', }; export const defaultRolloverAction: RolloverAction = { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/ui_metric.ts index e1a316eda594f..f87cc94dee091 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/ui_metric.ts @@ -19,3 +19,4 @@ export const UIM_CONFIG_WARM_PHASE: string = 'config_warm_phase'; export const UIM_CONFIG_SET_PRIORITY: string = 'config_set_priority'; export const UIM_INDEX_RETRY_STEP: string = 'index_retry_step'; export const UIM_EDIT_CLICK: string = 'edit_click'; +export const UIM_VIEW_CLICK: string = 'view_click'; 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 bb004766d89f7..bc5f93334f67f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -32,7 +32,7 @@ export const renderApp = ( executionContext: ExecutionContextStart, cloud?: CloudSetup ): UnmountCallback => { - const { navigateToUrl, getUrlForApp } = application; + const { navigateToUrl, getUrlForApp, capabilities } = application; const { overlays, http } = startServices; render( @@ -55,6 +55,7 @@ export const renderApp = ( overlays, http, history, + capabilities, }} > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/use_is_read_only.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/use_is_read_only.ts new file mode 100644 index 0000000000000..518dbc69e34b4 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/use_is_read_only.ts @@ -0,0 +1,19 @@ +/* + * 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 { PLUGIN } from '../../../common/constants'; +import { useKibana } from '../../shared_imports'; + +export const useIsReadOnly = () => { + const { + services: { capabilities }, + } = useKibana(); + const ilmCaps = capabilities[PLUGIN.ID]; + const savePermission = Boolean(ilmCaps.save); + const showPermission = Boolean(ilmCaps.show); + return !savePermission && showPermission; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/edit_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/edit_warning.tsx index 1690676cd28d7..c9e87ac137aed 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/edit_warning.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/edit_warning.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useEditPolicyContext } from '../edit_policy_context'; import { getIndicesListPath } from '../../../services/navigation'; import { useKibana } from '../../../../shared_imports'; -import { IndexTemplatesFlyout } from '../../../components/index_templates_flyout'; +import { IndexTemplatesFlyout } from '../../../components'; export const EditWarning: FunctionComponent = () => { const { isNewPolicy, indices, indexTemplates, policyName, policy } = useEditPolicyContext(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_searchable_snapshot_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_searchable_snapshot_field.tsx index 5e19894843ff6..1aaab5c6657cf 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_searchable_snapshot_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_searchable_snapshot_field.tsx @@ -8,22 +8,16 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiTextColor } from '@elastic/eui'; +import { useKibana } from '../../../../../../shared_imports'; +import { i18nTexts } from '../../../i18n_texts'; import { LearnMoreLink } from '../../learn_more_link'; import { ToggleFieldWithDescribedFormRow } from '../../described_form_row'; -import { useKibana } from '../../../../../../shared_imports'; export const DeleteSearchableSnapshotField: React.FunctionComponent = () => { const { docLinks } = useKibana().services; return ( - - - } + title={

{i18nTexts.editPolicy.deleteSearchableSnapshotLabel}

} description={ { return ( - {i18n.translate('xpack.indexLifecycleMgmt.hotPhase.rolloverFieldTitle', { - defaultMessage: 'Rollover', - })} - - } + title={

{i18nTexts.editPolicy.rolloverLabel}

} description={ <> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx index c45e172868938..50856c71ad9aa 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx @@ -7,13 +7,13 @@ import { get } from 'lodash'; import React, { FunctionComponent } from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiDescribedFormGroup, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; import { useKibana, useFormData } from '../../../../../../../shared_imports'; import { PhaseWithAllocation, DataTierRole } from '../../../../../../../../common/types'; import { getAvailableNodeRoleForPhase, isNodeRoleFirstPreference } from '../../../../../../lib'; import { useLoadNodes } from '../../../../../../services/api'; +import { i18nTexts } from '../../../../i18n_texts'; import { DataTierAllocationType } from '../../../../types'; import { @@ -30,12 +30,6 @@ import { import './_data_tier_allocation.scss'; -const i18nTexts = { - title: i18n.translate('xpack.indexLifecycleMgmt.common.dataTier.title', { - defaultMessage: 'Data allocation', - }), -}; - interface Props { phase: PhaseWithAllocation; description: React.ReactNode; @@ -188,7 +182,7 @@ export const DataTierAllocationField: FunctionComponent = ({ phase, descr return ( {i18nTexts.title}} + title={

{i18nTexts.editPolicy.dataAllocationLabel}

} description={ <> {description} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx index f846a033ce44a..4b7b55213ab38 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/downsample_field.tsx @@ -30,14 +30,7 @@ export const DownsampleField: React.FunctionComponent = ({ phase }) => { return ( - - - } + title={

{i18nTexts.editPolicy.downsampleLabel}

} description={ = ({ phase }) => { return ( - - - } + title={

{i18nTexts.editPolicy.forceMergeLabel}

} description={ <> = ({ phase }) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx index 979150a8d267b..8fe75aadd49d9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx @@ -5,9 +5,7 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import React, { FunctionComponent, useEffect } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; import { get } from 'lodash'; import { @@ -27,20 +25,7 @@ import { UseField, useConfiguration, useGlobalFields } from '../../../../form'; import { getPhaseMinAgeInMilliseconds } from '../../../../lib'; import { timeUnits } from '../../../../constants'; import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util'; - -const i18nTexts = { - rolloverToolTipDescription: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.minimumAge.rolloverToolTipDescription', - { - defaultMessage: - 'Data age is calculated from rollover. Rollover is configured in the hot phase.', - } - ), - minAgeUnitFieldSuffix: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.minimumAge.minimumAgeFieldSuffixLabel', - { defaultMessage: 'old' } - ), -}; +import { i18nTexts } from '../../../../i18n_texts'; interface Props { phase: PhaseWithTiming; @@ -95,10 +80,7 @@ export const MinAgeField: FunctionComponent = ({ phase }): React.ReactEle > - + {`${i18nTexts.editPolicy.minAgeLabel}:`} @@ -127,15 +109,15 @@ export const MinAgeField: FunctionComponent = ({ phase }): React.ReactEle
); const selectAppendValue: Array = isUsingRollover - ? [i18nTexts.minAgeUnitFieldSuffix, icon] - : [i18nTexts.minAgeUnitFieldSuffix]; + ? [i18nTexts.editPolicy.minAgeUnitFieldSuffix, icon] + : [i18nTexts.editPolicy.minAgeUnitFieldSuffix]; const unitValue = unitField.value as string; let unitOptions = timeUnits; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/readonly_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/readonly_field.tsx index 4283f357bff88..11cea37f67bba 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/readonly_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/readonly_field.tsx @@ -11,6 +11,7 @@ import { EuiTextColor } from '@elastic/eui'; import { LearnMoreLink } from '../../learn_more_link'; import { ToggleFieldWithDescribedFormRow } from '../../described_form_row'; import { useKibana } from '../../../../../../shared_imports'; +import { i18nTexts } from '../../../i18n_texts'; interface Props { phase: 'hot' | 'warm' | 'cold'; } @@ -19,14 +20,7 @@ export const ReadonlyField: React.FunctionComponent = ({ phase }) => { const { docLinks } = useKibana().services; return ( - - - } + title={

{i18nTexts.editPolicy.readonlyLabel}

} description={ = ({ phase }) => { @@ -24,13 +25,7 @@ export const ReplicasField: FunctionComponent = ({ phase }) => { const initialValue = policy.phases[phase]?.actions?.allocate?.number_of_replicas != null; return ( - {i18n.translate('xpack.indexLifecycleMgmt.numberOfReplicas.formRowTitle', { - defaultMessage: 'Replicas', - })} - - } + title={

{i18nTexts.editPolicy.replicasLabel}

} description={i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.numberOfReplicas.formRowDescription', { 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 f36067923e1b7..e98d1f1c2c55a 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 @@ -19,6 +19,7 @@ import { SearchableSnapshotDataProvider } from './searchable_snapshot_data_provi import { RepositoryComboBoxField } from './repository_combobox_field'; import './_searchable_snapshot_field.scss'; +import { i18nTexts as i18nTextsEdit } from '../../../../i18n_texts'; export interface Props { phase: 'hot' | 'cold' | 'frozen'; @@ -35,12 +36,7 @@ const geti18nTexts = ( case 'hot': case 'cold': return { - title: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.fullyMountedSearchableSnapshotField.title', - { - defaultMessage: 'Searchable snapshot', - } - ), + title: i18nTextsEdit.editPolicy.searchableSnapshotLabel, description: ( = ({ phase }) => { const { docLinks } = useKibana().services; return ( - - - } + title={

{i18nTexts.editPolicy.shrinkActionLabel}

} description={ = ({ phase }) => { titleSize="xs" switchProps={{ 'data-test-subj': `${phase}-shrinkSwitch`, - label: i18nTexts.editPolicy.shrinkLabel, + label: i18nTexts.editPolicy.shrinkToggleLabel, initialValue: Boolean(policy.phases[phase]?.actions?.shrink), }} fullWidth 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 e89189df7667b..f072db6b10b11 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 @@ -24,6 +24,7 @@ import { useLoadSnapshotPolicies } from '../../../../../services/api'; import { UseField } from '../../../form'; import { FieldLoadingError, LearnMoreLink, OptionalLabel } from '../..'; +import { i18nTexts } from '../../../i18n_texts'; const waitForSnapshotFormField = 'phases.delete.actions.wait_for_snapshot.policy'; @@ -145,14 +146,7 @@ export const SnapshotPoliciesField: React.FunctionComponent = () => { return ( - - - } + title={

{i18nTexts.editPolicy.waitForSnapshotLabel}

} description={ <> = memo( - ({ hasDeletePhase, isUsingRollover, ...phasesMinAge }) => { + ({ hasDeletePhase, isUsingRollover, showTitle = true, ...phasesMinAge }) => { const absoluteTimings: AbsoluteTimings = { hot: { min_age: phasesMinAge.hotPhaseMinAge }, warm: phasesMinAge.warmPhaseMinAge ? { min_age: phasesMinAge.warmPhaseMinAge } : undefined, @@ -147,24 +148,26 @@ export const Timeline: FunctionComponent = memo( return ( - - -

{i18nTexts.title}

-
- - {i18nTexts.description} -   - - } - /> - -
+ {showTitle && ( + + +

{i18nTexts.title}

+
+ + {i18nTexts.description} +   + + } + /> + +
+ )}
{ [originalPolicyName, existingPolicies, isClonedPolicy] ); - const backToPolicyList = () => { - history.push('/policies'); + const backToPolicyList = (name?: string) => { + const url = name ? getPolicyViewPath(name) : getPoliciesListPath(); + history.push(url); }; const submit = async () => { @@ -141,17 +143,18 @@ export const EditPolicy: React.FunctionComponent = () => { }) ); } else { + const name = getPolicyName(); setHasSubmittedForm(true); const success = await savePolicy( { ...policy, - name: getPolicyName(), + name, }, isNewPolicy || isClonedPolicy ); if (success) { - backToPolicyList(); + backToPolicyList(name); } } }; @@ -305,7 +308,10 @@ export const EditPolicy: React.FunctionComponent = () => { - + backToPolicyList()} + > ({ +const getPriorityField = (phase: 'hot' | 'warm' | 'cold') => ({ defaultValue: defaultIndexPriority[phase], label: i18nTexts.editPolicy.indexPriorityFieldLabel, validations: [ @@ -380,9 +372,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ actions: { rollover: { max_age: { - label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumAgeLabel', { - defaultMessage: 'Maximum age', - }), + label: i18nTexts.editPolicy.maxAgeLabel, validations: [ { validator: rolloverThresholdsValidator, @@ -397,9 +387,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ fieldsToValidateOnChange: rolloverFormPaths, }, max_docs: { - label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumDocumentsLabel', { - defaultMessage: 'Maximum documents', - }), + label: i18nTexts.editPolicy.maxDocsLabel, validations: [ { validator: rolloverThresholdsValidator, @@ -443,9 +431,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ fieldsToValidateOnChange: rolloverFormPaths, }, max_size: { - label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeLabel', { - defaultMessage: 'Maximum index size', - }), + label: i18nTexts.editPolicy.maxSizeLabel, validations: [ { validator: rolloverThresholdsValidator, @@ -505,12 +491,6 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ frozen: { min_age: getMinAgeField('frozen'), actions: { - allocate: { - number_of_replicas: numberOfReplicasField, - }, - set_priority: { - priority: getPriorityField('frozen'), - }, searchable_snapshot: searchableSnapshotFields, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 9c48efd6c2afa..4304168d1bb0e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -9,7 +9,10 @@ import { i18n } from '@kbn/i18n'; export const i18nTexts = { editPolicy: { - shrinkLabel: i18n.translate('xpack.indexLifecycleMgmt.shrink.enableShrinkLabel', { + shrinkActionLabel: i18n.translate('xpack.indexLifecycleMgmt.shrink.actionLabel', { + defaultMessage: 'Shrink', + }), + shrinkToggleLabel: i18n.translate('xpack.indexLifecycleMgmt.shrink.enableShrinkLabel', { defaultMessage: 'Shrink index', }), shrinkCountLabel: i18n.translate( @@ -18,6 +21,12 @@ export const i18nTexts = { defaultMessage: 'Configure shard count', } ), + shrinkNumberOfShardsLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.shrink.numberOfPrimaryShardsLabel', + { + defaultMessage: 'Number of primary shards', + } + ), shrinkSizeLabel: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.shrink.configureShardSizeLabel', { @@ -54,12 +63,18 @@ export const i18nTexts = { ), }, }, + forceMergeLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.forceMerge.enableText', { + defaultMessage: 'Force merge', + }), forceMergeEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.forcemerge.enableLabel', { defaultMessage: 'Force merge data', }), readonlyEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.readonlyFieldLabel', { defaultMessage: 'Make index read only', }), + readonlyLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.readonlyTitle', { + defaultMessage: 'Read only', + }), downsampleEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.downsampleFieldLabel', { defaultMessage: 'Enable downsampling', }), @@ -116,12 +131,6 @@ export const i18nTexts = { defaultMessage: 'Snapshot repository', } ), - searchableSnapshotsStorageFieldLabel: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.searchableSnapshotStorageFieldLabel', - { - defaultMessage: 'Searchable snapshot storage', - } - ), maxPrimaryShardSizeLabel: i18n.translate( 'xpack.indexLifecycleMgmt.hotPhase.maximumPrimaryShardSizeLabel', { @@ -140,6 +149,64 @@ export const i18nTexts = { defaultMessage: 'Maximum shard size units', } ), + maxAgeLabel: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumAgeLabel', { + defaultMessage: 'Maximum age', + }), + maxDocsLabel: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumDocumentsLabel', { + defaultMessage: 'Maximum documents', + }), + maxSizeLabel: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeLabel', { + defaultMessage: 'Maximum index size', + }), + downsampleLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.downsampleTitle', { + defaultMessage: 'Downsample', + }), + minAgeLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minimumAge.minimumAgeFieldLabel', + { defaultMessage: 'Move data into phase when' } + ), + rolloverLabel: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.rolloverFieldTitle', { + defaultMessage: 'Rollover', + }), + rolloverToolTipDescription: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minimumAge.rolloverToolTipDescription', + { + defaultMessage: + 'Data age is calculated from rollover. Rollover is configured in the hot phase.', + } + ), + minAgeUnitFieldSuffix: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minimumAge.minimumAgeFieldSuffixLabel', + { defaultMessage: 'old' } + ), + replicasLabel: i18n.translate('xpack.indexLifecycleMgmt.numberOfReplicas.formRowTitle', { + defaultMessage: 'Replicas', + }), + numberOfReplicasLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.numberOfReplicasLabel', + { + defaultMessage: 'Number of replicas', + } + ), + dataAllocationLabel: i18n.translate('xpack.indexLifecycleMgmt.common.dataTier.title', { + defaultMessage: 'Data allocation', + }), + searchableSnapshotLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.fullyMountedSearchableSnapshotField.title', + { + defaultMessage: 'Searchable snapshot', + } + ), + waitForSnapshotLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotTitle', + { defaultMessage: 'Wait for snapshot policy' } + ), + deleteSearchableSnapshotLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.deletePhase.deleteSearchableSnapshotTitle', + { + defaultMessage: 'Delete searchable snapshot', + } + ), errors: { numberRequired: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.errors.numberRequiredErrorMessage', diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 5dd5477cae2c2..4d4d561eedb96 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -78,7 +78,7 @@ interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField, Dow readonlyEnabled: boolean; } -interface FrozenPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { +interface FrozenPhaseMetaFields extends MinAgeField { enabled: boolean; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/deprecated_policy_badge.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/deprecated_policy_badge.tsx new file mode 100644 index 0000000000000..f48e6d5c4a639 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/deprecated_policy_badge.tsx @@ -0,0 +1,32 @@ +/* + * 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 from 'react'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const deprecatedPolicyTooltips = { + badge: i18n.translate('xpack.indexLifecycleMgmt.policyTable.templateBadgeType.deprecatedLabel', { + defaultMessage: 'Deprecated', + }), + badgeTooltip: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.templateBadgeType.deprecatedDescription', + { + defaultMessage: + 'This policy is no longer supported and might be removed in a future release. Instead, use one of the other policies available or create a new one.', + } + ), +}; +export const DeprecatedPolicyBadge = () => { + return ( + + + {deprecatedPolicyTooltips.badge} + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/index.ts new file mode 100644 index 0000000000000..0ff6731e542c4 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/index.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export { AddPolicyToTemplateConfirmModal } from './add_policy_to_template_confirm_modal'; +export { ConfirmDelete } from './confirm_delete'; +export { DeprecatedPolicyBadge } from './deprecated_policy_badge'; +export { ListActionHandler } from './list_action_handler'; +export { ManagedPolicyBadge } from './managed_policy_badge'; +export { PolicyTable } from './policy_table'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/list_action_handler.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/list_action_handler.tsx index c7d2183ec9481..9dd3eba08769a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/list_action_handler.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/list_action_handler.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { usePolicyListContext } from '../policy_list_context'; -import { IndexTemplatesFlyout } from '../../../components/index_templates_flyout'; -import { ConfirmDelete } from './confirm_delete'; -import { AddPolicyToTemplateConfirmModal } from './add_policy_to_template_confirm_modal'; +import { IndexTemplatesFlyout } from '../../../components'; +import { ViewPolicyFlyout } from '../policy_flyout'; +import { ConfirmDelete, AddPolicyToTemplateConfirmModal } from '.'; interface Props { - updatePolicies: () => void; + deletePolicyCallback: () => void; } -export const ListActionHandler: React.FunctionComponent = ({ updatePolicies }) => { +export const ListActionHandler: React.FunctionComponent = ({ deletePolicyCallback }) => { const { listAction, setListAction } = usePolicyListContext(); if (listAction?.actionType === 'viewIndexTemplates') { return ( @@ -32,7 +32,7 @@ export const ListActionHandler: React.FunctionComponent = ({ updatePolici { - updatePolicies(); + deletePolicyCallback(); setListAction(null); }} onCancel={() => { @@ -58,5 +58,10 @@ export const ListActionHandler: React.FunctionComponent = ({ updatePolici /> ); } + + if (listAction?.actionType === 'viewPolicy') { + return ; + } + return null; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/managed_policy_badge.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/managed_policy_badge.tsx new file mode 100644 index 0000000000000..f8124fcd70474 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/managed_policy_badge.tsx @@ -0,0 +1,33 @@ +/* + * 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 from 'react'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const managedPolicyTooltips = { + badge: i18n.translate('xpack.indexLifecycleMgmt.policyTable.templateBadgeType.managedLabel', { + defaultMessage: 'Managed', + }), + badgeTooltip: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.templateBadgeType.managedDescription', + { + defaultMessage: + 'This policy is preconfigured and managed by Elastic; editing or deleting this policy might break Kibana.', + } + ), +}; + +export const ManagedPolicyBadge = () => { + return ( + + + {managedPolicyTooltips.badge} + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx index 0133c5034bb72..b7f77c6d1c77a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx @@ -11,8 +11,6 @@ import { EuiLink, EuiInMemoryTable, EuiToolTip, - EuiButtonIcon, - EuiBadge, EuiFlexItem, EuiSwitch, EuiSearchBarProps, @@ -28,59 +26,30 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { useEuiTablePersist } from '@kbn/shared-ux-table-persist'; +import { hasLinkedIndices } from '../../../lib/policies'; import { useStateWithLocalStorage } from '../../../lib/settings_local_storage'; import { PolicyFromES } from '../../../../../common/types'; import { useKibana } from '../../../../shared_imports'; -import { getIndicesListPath, getPolicyEditPath } from '../../../services/navigation'; +import { + getIndicesListPath, + getPolicyEditPath, + getPolicyViewPath, +} from '../../../services/navigation'; import { trackUiMetric } from '../../../services/ui_metric'; +import { UIM_VIEW_CLICK } from '../../../constants'; -import { UIM_EDIT_CLICK } from '../../../constants'; -import { hasLinkedIndices } from '../../../lib/policies'; import { usePolicyListContext } from '../policy_list_context'; +import { ManagedPolicyBadge, DeprecatedPolicyBadge } from '.'; +import { useIsReadOnly } from '../../../lib/use_is_read_only'; const actionTooltips = { - deleteEnabled: i18n.translate('xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonText', { - defaultMessage: 'Delete policy', - }), - deleteDisabled: i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonDisabledTooltip', - { - defaultMessage: 'You cannot delete a policy that is being used by an index', - } - ), viewIndices: i18n.translate('xpack.indexLifecycleMgmt.policyTable.viewIndicesButtonText', { defaultMessage: 'View indices linked to policy', }), - addIndexTemplate: i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.addPolicyToTemplateButtonText', - { - defaultMessage: 'Add policy to index template', - } - ), -}; - -const managedPolicyTooltips = { - badge: i18n.translate('xpack.indexLifecycleMgmt.policyTable.templateBadgeType.managedLabel', { - defaultMessage: 'Managed', - }), - badgeTooltip: i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.templateBadgeType.managedDescription', + viewIndexTemplates: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.viewIndexTemplatesButtonText', { - defaultMessage: - 'This policy is preconfigured and managed by Elastic; editing or deleting this policy might break Kibana.', - } - ), -}; - -const deprecatedPolicyTooltips = { - badge: i18n.translate('xpack.indexLifecycleMgmt.policyTable.templateBadgeType.deprecatedLabel', { - defaultMessage: 'Deprecated', - }), - badgeTooltip: i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.templateBadgeType.deprecatedDescription', - { - defaultMessage: - 'This policy is no longer supported and might be removed in a future release. Instead, use one of the other policies available or create a new one.', + defaultMessage: 'View index templates linked to policy', } ), }; @@ -94,7 +63,7 @@ const PAGE_SIZE_OPTIONS = [10, 25, 50]; export const PolicyTable: React.FunctionComponent = ({ policies }) => { const [query, setQuery] = useState(''); - + const isReadOnly = useIsReadOnly(); const history = useHistory(); const { services: { getUrlForApp }, @@ -188,8 +157,8 @@ export const PolicyTable: React.FunctionComponent = ({ policies }) => { - trackUiMetric(METRIC_TYPE.CLICK, UIM_EDIT_CLICK) + {...reactRouterNavigate(history, getPolicyViewPath(value), () => + trackUiMetric(METRIC_TYPE.CLICK, UIM_VIEW_CLICK) )} > {value} @@ -198,22 +167,14 @@ export const PolicyTable: React.FunctionComponent = ({ policies }) => { {isDeprecated && ( <>   - - - {deprecatedPolicyTooltips.badge} - - + )} {isManaged && ( <>   - - - {managedPolicyTooltips.badge} - - + )} @@ -229,7 +190,7 @@ export const PolicyTable: React.FunctionComponent = ({ policies }) => { sortable: ({ indexTemplates }) => (indexTemplates ?? []).length, render: (value: string[], policy: PolicyFromES) => { return value && value.length > 0 ? ( - + = ({ policies }) => { return value ? moment(value).format('MMM D, YYYY') : value; }, }, - { + ]; + if (!isReadOnly) { + columns.push({ actions: [ { - render: (policy: PolicyFromES) => { - return ( - - - setListAction({ selectedPolicy: policy, actionType: 'addIndexTemplate' }) - } - iconType="plusInCircle" - aria-label={actionTooltips.addIndexTemplate} - /> - - ); - }, + isPrimary: true, + name: i18n.translate('xpack.indexLifecycleMgmt.policyTable.editActionLabel', { + defaultMessage: 'Edit', + }), + description: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.editActionDescription', + { + defaultMessage: 'Edit this policy', + } + ), + type: 'icon', + icon: 'pencil', + onClick: ({ name }) => history.push(getPolicyEditPath(name)), + 'data-test-subj': 'editPolicy', }, + + { + name: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.addToIndexTemplateActionLabel', + { + defaultMessage: 'Add to index template', + } + ), + description: i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.addToIndexTemplateActionDescription', + { defaultMessage: 'Add policy to index template' } + ), + type: 'icon', + icon: 'plusInCircle', + onClick: (policy) => + setListAction({ selectedPolicy: policy, actionType: 'addIndexTemplate' }), + 'data-test-subj': 'addPolicyToTemplate', + }, + { - render: (policy: PolicyFromES, enabled: boolean) => { - return ( - - - setListAction({ selectedPolicy: policy, actionType: 'deletePolicy' }) + isPrimary: true, + name: i18n.translate('xpack.indexLifecycleMgmt.policyTable.deleteActionLabel', { + defaultMessage: 'Delete', + }), + description: (policy) => { + return hasLinkedIndices(policy) + ? i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonDisabledTooltip', + { + defaultMessage: 'You cannot delete a policy that is being used by an index', } - iconType="trash" - aria-label={actionTooltips.deleteEnabled} - disabled={!enabled} - /> - - ); + ) + : i18n.translate('xpack.indexLifecycleMgmt.policyTable.deleteActionDescription', { + defaultMessage: 'Delete this policy', + }); }, - enabled: (policy: PolicyFromES) => !hasLinkedIndices(policy), + type: 'icon', + icon: 'trash', + color: 'danger', + onClick: (policy) => + setListAction({ selectedPolicy: policy, actionType: 'deletePolicy' }), + enabled: (policy) => !hasLinkedIndices(policy), + 'data-test-subj': 'deletePolicy', }, ], name: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.actionsHeader', { defaultMessage: 'Actions', }), - }, - ]; + 'data-test-subj': 'policyActionsCollapsedButton', + }); + } return ( { + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/action_description.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/action_description.tsx new file mode 100644 index 0000000000000..5a4f6905fe796 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/action_description.tsx @@ -0,0 +1,39 @@ +/* + * 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, { ReactNode } from 'react'; +import { + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +export const ActionDescription = ({ + title, + descriptionItems, +}: { + title: string; + descriptionItems?: string[] | ReactNode[]; +}) => { + return ( + <> + {title} + {descriptionItems && ( + + {descriptionItems.map((descriptionItem, index) => ( + + + {descriptionItem} + + ))} + + )} + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/data_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/data_allocation.tsx new file mode 100644 index 0000000000000..1970b78eaac50 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/data_allocation.tsx @@ -0,0 +1,85 @@ +/* + * 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 from 'react'; + +import { EuiBadge, EuiCode } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + AllocateAction, + PhaseWithAllocation, + SerializedColdPhase, + SerializedWarmPhase, +} from '../../../../../../common/types'; +import { determineDataTierAllocationType } from '../../../../lib'; +import type { ActionComponentProps } from './types'; +import { ActionDescription } from './action_description'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; + +const getAllocationDescription = ( + type: ReturnType, + phase: PhaseWithAllocation, + allocate?: AllocateAction +) => { + if (type === 'none') { + return i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.dataAllocationDisabledLabel', { + defaultMessage: 'Disabled', + }); + } + if (type === 'node_roles') { + const label = + phase === 'warm' + ? i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.dataAllocationWarmNodesLabel', { + defaultMessage: 'Using warm nodes', + }) + : i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.dataAllocationColdNodesdLabel', { + defaultMessage: 'Using cold nodes', + }); + return ( + <> + {label}{' '} + + + + + ); + } + if (type === 'node_attrs') { + return ( + <> + {i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.dataAllocationAttributtesLabel', { + defaultMessage: 'Node attributes', + })} + {': '} + {JSON.stringify(allocate?.require)} + + ); + } +}; + +export const DataAllocation = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const allocate = (phaseConfig as SerializedWarmPhase | SerializedColdPhase)?.actions.allocate; + const migrate = (phaseConfig as SerializedWarmPhase | SerializedColdPhase)?.actions.migrate; + const allocationType = determineDataTierAllocationType({ allocate, migrate }); + const allocationDescription = getAllocationDescription( + allocationType, + phase as PhaseWithAllocation, + allocate + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/delete_searchable_snapshot.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/delete_searchable_snapshot.tsx new file mode 100644 index 0000000000000..c5e22e9329733 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/delete_searchable_snapshot.tsx @@ -0,0 +1,27 @@ +/* + * 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 from 'react'; +import { i18nTexts as i18nTextsFlyout } from './i18n_texts'; +import { SerializedDeletePhase } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const DeleteSearchableSnapshot = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const deleteSearchableSnapshot = (phaseConfig as SerializedDeletePhase)?.actions.delete + ?.delete_searchable_snapshot; + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/downsample.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/downsample.tsx new file mode 100644 index 0000000000000..f3d4780b48906 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/downsample.tsx @@ -0,0 +1,27 @@ +/* + * 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 from 'react'; +import { PhaseWithDownsample } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const Downsample = ({ phase, phases }: ActionComponentProps) => { + const downsample = phases[phase as PhaseWithDownsample]?.actions.downsample; + return downsample ? ( + + {`${i18nTexts.editPolicy.downsampleIntervalFieldLabel}: `} + {downsample.fixed_interval} + , + ]} + /> + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/forcemerge.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/forcemerge.tsx new file mode 100644 index 0000000000000..9bb1fa1e3dd60 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/forcemerge.tsx @@ -0,0 +1,38 @@ +/* + * 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 from 'react'; +import { SerializedHotPhase, SerializedWarmPhase } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; + +import { i18nTexts as i18nTextsFlyout } from './i18n_texts'; +import type { ActionComponentProps } from './types'; +import { ActionDescription } from './action_description'; + +export const Forcemerge = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const forcemerge = (phaseConfig as SerializedHotPhase | SerializedWarmPhase)?.actions.forcemerge; + return forcemerge ? ( + + {`${i18nTexts.editPolicy.maxNumSegmentsFieldLabel}: `} + {forcemerge.max_num_segments} + , + <> + {`${i18nTexts.editPolicy.bestCompressionFieldLabel}: `} + + {forcemerge.index_codec === 'best_compression' + ? i18nTextsFlyout.yes + : i18nTextsFlyout.no} + + , + ]} + /> + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/i18n_texts.ts new file mode 100644 index 0000000000000..b5a00bac82bf7 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/i18n_texts.ts @@ -0,0 +1,17 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const i18nTexts = { + yes: i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.yesLabel', { + defaultMessage: 'Yes', + }), + no: i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.noLabel', { + defaultMessage: 'No', + }), +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index.ts new file mode 100644 index 0000000000000..5166b62f8e579 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export { Rollover } from './rollover'; +export { MinAge } from './min_age'; +export { Forcemerge } from './forcemerge'; +export { Shrink } from './shrink'; +export { SearchableSnapshot } from './searchable_snapshot'; +export { Downsample } from './downsample'; +export { Readonly } from './readonly'; +export { IndexPriority } from './index_priority'; +export { Replicas } from './replicas'; +export { DataAllocation } from './data_allocation'; +export { WaitForSnapshot } from './wait_for_snapshot'; +export { DeleteSearchableSnapshot } from './delete_searchable_snapshot'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index_priority.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index_priority.tsx new file mode 100644 index 0000000000000..e470fc1c968d6 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index_priority.tsx @@ -0,0 +1,29 @@ +/* + * 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 from 'react'; +import { + SerializedHotPhase, + SerializedWarmPhase, + SerializedColdPhase, +} from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const IndexPriority = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const indexPriority = ( + phaseConfig as SerializedHotPhase | SerializedWarmPhase | SerializedColdPhase + )?.actions.set_priority; + return indexPriority ? ( + + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/min_age.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/min_age.tsx new file mode 100644 index 0000000000000..e6c568ecdebe6 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/min_age.tsx @@ -0,0 +1,21 @@ +/* + * 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 from 'react'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const MinAge = ({ phase, phases }: ActionComponentProps) => { + const minAge = phases[phase]?.min_age; + return minAge ? ( + + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/readonly.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/readonly.tsx new file mode 100644 index 0000000000000..3d7163be382a0 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/readonly.tsx @@ -0,0 +1,23 @@ +/* + * 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 from 'react'; +import { + SerializedColdPhase, + SerializedHotPhase, + SerializedWarmPhase, +} from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const Readonly = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const readonly = (phaseConfig as SerializedHotPhase | SerializedWarmPhase | SerializedColdPhase) + ?.actions.readonly; + return readonly ? : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/replicas.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/replicas.tsx new file mode 100644 index 0000000000000..de2dee8f1aa89 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/replicas.tsx @@ -0,0 +1,28 @@ +/* + * 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 from 'react'; +import { PhaseWithAllocation } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; + +import type { ActionComponentProps } from './types'; +import { ActionDescription } from './action_description'; + +export const Replicas = ({ phase, phases }: ActionComponentProps) => { + const allocate = phases[phase as PhaseWithAllocation]?.actions.allocate; + return allocate?.number_of_replicas !== undefined ? ( + + {`${i18nTexts.editPolicy.numberOfReplicasLabel}: `} + {allocate.number_of_replicas} + , + ]} + /> + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/rollover.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/rollover.tsx new file mode 100644 index 0000000000000..d691a99728a6b --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/rollover.tsx @@ -0,0 +1,76 @@ +/* + * 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 from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { SerializedHotPhase } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const Rollover = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const rollover = (phaseConfig as SerializedHotPhase)?.actions.rollover; + const descriptionItems = []; + if (rollover?.max_primary_shard_size) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.maxPrimaryShardSizeLabel}: `} + {rollover.max_primary_shard_size} + + ); + } + if (rollover?.max_primary_shard_docs) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.maxPrimaryShardDocsLabel}: `} + {rollover.max_primary_shard_docs} + + ); + } + + if (rollover?.max_age) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.maxAgeLabel}: `} + {rollover.max_age} + + ); + } + + if (rollover?.max_docs) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.maxDocsLabel}: `} + {rollover.max_docs} + + ); + } + + if (rollover?.max_size) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.maxSizeLabel}: `} + {rollover.max_size}{' '} + + + + + ); + } + + return rollover ? ( + + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/searchable_snapshot.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/searchable_snapshot.tsx new file mode 100644 index 0000000000000..4b30783f4c7fd --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/searchable_snapshot.tsx @@ -0,0 +1,35 @@ +/* + * 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 from 'react'; +import { EuiCode } from '@elastic/eui'; +import { + SerializedColdPhase, + SerializedFrozenPhase, + SerializedHotPhase, +} from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const SearchableSnapshot = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const searchableSnapshot = ( + phaseConfig as SerializedHotPhase | SerializedColdPhase | SerializedFrozenPhase + ).actions?.searchable_snapshot; + return searchableSnapshot ? ( + + {`${i18nTexts.editPolicy.searchableSnapshotsRepoFieldLabel}: `} + {searchableSnapshot.snapshot_repository} + , + ]} + /> + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/shrink.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/shrink.tsx new file mode 100644 index 0000000000000..2155a380ee1cd --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/shrink.tsx @@ -0,0 +1,49 @@ +/* + * 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 from 'react'; +import { SerializedHotPhase, SerializedWarmPhase } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { i18nTexts as i18nTextsFlyout } from './i18n_texts'; +import type { ActionComponentProps } from './types'; +import { ActionDescription } from './action_description'; + +export const Shrink = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const shrink = (phaseConfig as SerializedHotPhase | SerializedWarmPhase)?.actions.shrink; + const descriptionItems = []; + if (shrink?.number_of_shards) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.shrinkNumberOfShardsLabel}: `} + {shrink.number_of_shards} + + ); + } + if (shrink?.max_primary_shard_size) { + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.maxPrimaryShardSizeLabel}: `} + {shrink.max_primary_shard_size} + + ); + } + + descriptionItems.push( + <> + {`${i18nTexts.editPolicy.allowWriteAfterShrinkLabel}: `} + {shrink?.allow_write_after_shrink ? i18nTextsFlyout.yes : i18nTextsFlyout.no} + + ); + + return shrink ? ( + + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/types.ts new file mode 100644 index 0000000000000..5407836972f55 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/types.ts @@ -0,0 +1,13 @@ +/* + * 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 type { Phase, Phases } from '../../../../../../common/types'; + +export interface ActionComponentProps { + phase: Phase; + phases: Phases; +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/wait_for_snapshot.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/wait_for_snapshot.tsx new file mode 100644 index 0000000000000..216a0f08ed932 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/wait_for_snapshot.tsx @@ -0,0 +1,24 @@ +/* + * 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 from 'react'; +import { EuiCode } from '@elastic/eui'; +import { SerializedDeletePhase } from '../../../../../../common/types'; +import { i18nTexts } from '../../../edit_policy/i18n_texts'; +import { ActionDescription } from './action_description'; +import type { ActionComponentProps } from './types'; + +export const WaitForSnapshot = ({ phase, phases }: ActionComponentProps) => { + const phaseConfig = phases[phase]; + const waitForSnapshot = (phaseConfig as SerializedDeletePhase).actions?.wait_for_snapshot; + return waitForSnapshot ? ( + {waitForSnapshot.policy}]} + /> + ) : null; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/delete_phase.tsx new file mode 100644 index 0000000000000..cf4b05bc7504a --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/delete_phase.tsx @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/* + * 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 from 'react'; +import { PhaseDescription } from './phase_description'; +import { Phases } from '../../../../../common/types'; +import { MinAge, WaitForSnapshot, DeleteSearchableSnapshot } from './components'; + +export const DeletePhase = ({ phases }: { phases: Phases }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/frozen_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/frozen_phase.tsx new file mode 100644 index 0000000000000..597d62274f912 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/frozen_phase.tsx @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/* + * 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 from 'react'; +import { PhaseDescription } from './phase_description'; +import { Phases } from '../../../../../common/types'; +import { MinAge, SearchableSnapshot } from './components'; + +export const FrozenPhase = ({ phases }: { phases: Phases }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/hot_phase.tsx new file mode 100644 index 0000000000000..57b5b595a65ab --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/hot_phase.tsx @@ -0,0 +1,37 @@ +/* + * 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 from 'react'; +import { PhaseDescription } from './phase_description'; +import { Phases } from '../../../../../common/types'; +import { + Rollover, + Forcemerge, + Shrink, + SearchableSnapshot, + Downsample, + Readonly, + IndexPriority, +} from './components'; + +export const HotPhase = ({ phases }: { phases: Phases }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/index.ts new file mode 100644 index 0000000000000..9e0738710860c --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { ViewPolicyFlyout } from './view_policy_flyout'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_description.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_description.tsx new file mode 100644 index 0000000000000..16c4bf2d4ebbb --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_description.tsx @@ -0,0 +1,43 @@ +/* + * 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, { ComponentType } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiDescriptionList } from '@elastic/eui'; +import { PhaseIndicator } from './phase_indicator'; +import { ActionComponentProps } from './components/types'; +import { i18nTexts } from '../../edit_policy/i18n_texts'; + +export const PhaseDescription = ({ + phase, + phases, + components, +}: ActionComponentProps & { + components: Array>; +}) => { + const title = i18nTexts.editPolicy.titles[phase]; + return ( + <> + + + + + + +

{title}

+
+
+
+ + + {components.map((Component, index) => ( + + ))} + + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_indicator.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_indicator.tsx new file mode 100644 index 0000000000000..6c5fd35dcdcfd --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_indicator.tsx @@ -0,0 +1,32 @@ +/* + * 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 from 'react'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import type { Phase } from '../../../../../common/types'; + +const phaseToIndicatorColors = { + hot: euiThemeVars.euiColorVis9, + warm: euiThemeVars.euiColorVis5, + cold: euiThemeVars.euiColorVis1, + frozen: euiThemeVars.euiColorVis4, + delete: euiThemeVars.euiColorLightShade, +}; +export const PhaseIndicator = ({ phase }: { phase: Phase }) => { + return ( +
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/timeline.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/timeline.tsx new file mode 100644 index 0000000000000..1db8da207f490 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/timeline.tsx @@ -0,0 +1,31 @@ +/* + * 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 from 'react'; +import { PolicyFromES } from '../../../../../common/types'; +import { Timeline as ViewComponent } from '../../edit_policy/components/timeline/timeline'; + +export const Timeline = ({ policy }: { policy: PolicyFromES }) => { + const hasDeletePhase = Boolean(policy.policy.phases.delete); + const isUsingRollover = Boolean(policy.policy.phases.hot?.actions.rollover); + const warmPhaseMinAge = policy.policy.phases.warm?.min_age; + const coldPhaseMinAge = policy.policy.phases.cold?.min_age; + const frozenPhaseMinAge = policy.policy.phases.frozen?.min_age; + const deletePhaseMinAge = policy.policy.phases.delete?.min_age; + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/view_policy_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/view_policy_flyout.tsx new file mode 100644 index 0000000000000..e6576691c4af4 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/view_policy_flyout.tsx @@ -0,0 +1,204 @@ +/* + * 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, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { + EuiButton, + EuiButtonEmpty, + EuiContextMenu, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiIcon, + EuiPopover, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { PolicyFromES } from '../../../../../common/types'; +import { trackUiMetric } from '../../../services/ui_metric'; +import { hasLinkedIndices } from '../../../lib/policies'; +import { getPoliciesListPath, getPolicyEditPath } from '../../../services/navigation'; +import { UIM_EDIT_CLICK } from '../../../constants'; +import { useIsReadOnly } from '../../../lib/use_is_read_only'; +import { usePolicyListContext } from '../policy_list_context'; +import { DeprecatedPolicyBadge, ManagedPolicyBadge } from '../components'; +import { HotPhase } from './hot_phase'; +import { WarmPhase } from './warm_phase'; +import { Timeline } from './timeline'; +import { ColdPhase } from './cold_phase'; +import { DeletePhase } from './delete_phase'; +import { FrozenPhase } from './frozen_phase'; + +export const ViewPolicyFlyout = ({ policy }: { policy: PolicyFromES }) => { + const isReadOnly = useIsReadOnly(); + const { setListAction } = usePolicyListContext(); + const history = useHistory(); + const onClose = () => { + history.push(getPoliciesListPath()); + }; + const onEdit = (policyName: string) => { + trackUiMetric(METRIC_TYPE.CLICK, UIM_EDIT_CLICK); + history.push(getPolicyEditPath(policyName)); + }; + const [showPopover, setShowPopover] = useState(false); + const actionMenuItems = [ + /** + * Edit policy + */ + { + name: i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.editActionLabel', { + defaultMessage: 'Edit', + }), + icon: , + onClick: () => onEdit(policy.name), + }, + /** + * Add policy to index template + */ + { + name: i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.addToIndexTemplate', { + defaultMessage: 'Add to index template', + }), + icon: , + onClick: () => setListAction({ selectedPolicy: policy, actionType: 'addIndexTemplate' }), + }, + ]; + /** + * Delete policy + */ + if (!hasLinkedIndices(policy)) { + actionMenuItems.push({ + name: i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.deleteActionLabel', { + defaultMessage: 'Delete', + }), + icon: , + onClick: () => { + setShowPopover(false); + setListAction({ selectedPolicy: policy, actionType: 'deletePolicy' }); + }, + }); + } + + const managePolicyButton = ( + setShowPopover((previousBool) => !previousBool)} + iconType="arrowUp" + iconSide="right" + fill + data-test-subj="managePolicyButton" + > + {i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.managePolicyButtonLabel', { + defaultMessage: 'Manage', + })} + + ); + + return ( + + + + + +

{policy.name}

+
+
+ {policy.policy.deprecated ? ( + + {' '} + + + ) : null} + {policy.policy?._meta?.managed ? ( + + {' '} + + + ) : null} +
+
+ + + {/* Timeline */} + + + + {/* Hot phase */} + {policy.policy.phases.hot && } + + {/* Warm phase */} + {policy.policy.phases.warm && } + + {/* Cold phase */} + {policy.policy.phases.cold && } + + {/* Frozen phase */} + {policy.policy.phases.frozen && } + + {/* Delete phase */} + {policy.policy.phases.delete && } + + + + + + + {i18n.translate('xpack.indexLifecycleMgmt.policyFlyout.closeButtonLabel', { + defaultMessage: 'Close', + })} + + + {!isReadOnly && ( + + + setShowPopover(false)} + button={managePolicyButton} + panelPaddingSize="none" + repositionOnScroll + > + + + + + )} + + +
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/warm_phase.tsx new file mode 100644 index 0000000000000..70bc15e4da4f4 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/warm_phase.tsx @@ -0,0 +1,39 @@ +/* + * 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 from 'react'; +import { PhaseDescription } from './phase_description'; +import { Phases } from '../../../../../common/types'; +import { + MinAge, + Replicas, + Forcemerge, + Shrink, + Downsample, + Readonly, + IndexPriority, + DataAllocation, +} from './components'; + +export const WarmPhase = ({ phases }: { phases: Phases }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list.tsx index 0a81f6b16bf43..8894e4e63928c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list.tsx @@ -5,16 +5,18 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButton, EuiSpacer, EuiPageHeader, EuiPageTemplate } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; +import { usePolicyListContext } from './policy_list_context'; +import { useIsReadOnly } from '../../lib/use_is_read_only'; import { PolicyFromES } from '../../../../common/types'; -import { PolicyTable } from './components/policy_table'; -import { getPolicyCreatePath } from '../../services/navigation'; -import { ListActionHandler } from './components/list_action_handler'; +import { getPoliciesListPath, getPolicyCreatePath } from '../../services/navigation'; +import { PolicyTable, ListActionHandler } from './components'; +import { ViewPolicyFlyout } from './policy_flyout'; interface Props { policies: PolicyFromES[]; @@ -23,6 +25,19 @@ interface Props { export const PolicyList: React.FunctionComponent = ({ policies, updatePolicies }) => { const history = useHistory(); + const isReadOnly = useIsReadOnly(); + const { setListAction } = usePolicyListContext(); + const [flyoutPolicy, setFlyoutPolicy] = useState(null); + useEffect(() => { + const params = new URLSearchParams(history.location.search); + const policyParam = decodeURIComponent(params.get('policy') ?? ''); + const policyFromParam = policies.find((policy) => policy.name === policyParam); + if (policyFromParam) { + setFlyoutPolicy(policyFromParam); + } else { + setFlyoutPolicy(null); + } + }, [history.location.search, policies, setListAction]); const createPolicyButton = ( = ({ policies, updatePol ); } + const rightSideItems = isReadOnly ? [] : [createPolicyButton]; return ( <> - + { + // if a flyout was open, then close it + history.push(getPoliciesListPath()); + // update the policies in the list after 1 was deleted + updatePolicies(); + }} + /> = ({ policies, updatePol /> } bottomBorder - rightSideItems={[createPolicyButton]} + rightSideItems={rightSideItems} /> + + {flyoutPolicy && } ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list_context.tsx index a77aac9ad2f11..bf20ac66b3e07 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_list_context.tsx @@ -9,7 +9,7 @@ import React, { createContext, ReactChild, useContext, useState } from 'react'; import { PolicyFromES } from '../../../../common/types'; interface ListAction { - actionType: 'viewIndexTemplates' | 'addIndexTemplate' | 'deletePolicy'; + actionType: 'viewIndexTemplates' | 'addIndexTemplate' | 'deletePolicy' | 'viewPolicy'; selectedPolicy: PolicyFromES; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts index f967f503a79a7..bab80c67cbf8e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts @@ -18,6 +18,10 @@ export const getPolicyEditPath = (policyName: string): string => { return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; +export const getPolicyViewPath = (policyName: string): string => { + return encodeURI(`/policies?policy=${encodeURIComponent(policyName)}`); +}; + export const getPolicyCreatePath = () => { return ROUTES.create; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts index 6ea4c4d2b18ae..a1a58e80d8301 100644 --- a/x-pack/plugins/index_lifecycle_management/public/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/types.ts @@ -46,4 +46,5 @@ export interface AppServicesContext { overlays: OverlayStart; http: HttpSetup; history: ScopedHistory; + capabilities: ApplicationStart['capabilities']; } diff --git a/x-pack/plugins/index_lifecycle_management/server/plugin.ts b/x-pack/plugins/index_lifecycle_management/server/plugin.ts index 8c72c3408268f..0d88acbaaa4ff 100644 --- a/x-pack/plugins/index_lifecycle_management/server/plugin.ts +++ b/x-pack/plugins/index_lifecycle_management/server/plugin.ts @@ -75,7 +75,11 @@ export class IndexLifecycleManagementServerPlugin implements Plugin { - const link = await findPolicyLinkInListView(POLICY_NAME); - await link.click(); + const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); + + const editPolicyButton = await policyRow.findByTestSubject('editPolicy'); + await editPolicyButton.click(); + await retry.waitFor('ILM edit form', async () => { return ( (await testSubjects.getVisibleText('policyTitle')) === `Edit policy ${POLICY_NAME}` @@ -143,8 +146,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Request flyout', async () => { - const link = await findPolicyLinkInListView(POLICY_NAME); - await link.click(); + const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); + + const editPolicyButton = await policyRow.findByTestSubject('editPolicy'); + await editPolicyButton.click(); + await retry.waitFor('ILM request button', async () => { return testSubjects.exists('requestButton'); }); @@ -160,11 +166,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); + it('View policy flyout', async () => { + const link = await findPolicyLinkInListView(POLICY_NAME); + await link.click(); + + await retry.waitFor('View policy flyout to be present', async () => { + return testSubjects.isDisplayed('policyFlyoutTitle'); + }); + + await a11y.testAppSnapshot(); + }); + it('Add policy to index template modal', async () => { await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); - const addPolicyButton = await policyRow.findByTestSubject('addPolicyToTemplate'); + const actionsButton = await policyRow.findByTestSubject('euiCollapsedItemActionsButton'); + await actionsButton.click(); + + const addPolicyButton = await testSubjects.find('addPolicyToTemplate'); await addPolicyButton.click(); await retry.waitFor('ILM add policy to index template modal to be present', async () => { @@ -177,8 +197,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Delete policy modal', async () => { await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); - const deleteButton = await policyRow.findByTestSubject('deletePolicy'); + const deleteButton = await policyRow.findByTestSubject('deletePolicy'); await deleteButton.click(); await retry.waitFor('ILM delete policy modal to be present', async () => { @@ -191,10 +211,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Index templates flyout', async () => { await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); - const actionsButton = await policyRow.findByTestSubject('viewIndexTemplates'); + const actionsButton = await policyRow.findByTestSubject('euiCollapsedItemActionsButton'); await actionsButton.click(); + const templatesButton = await testSubjects.find('viewIndexTemplates'); + await templatesButton.click(); + const flyoutTitleSelector = 'indexTemplatesFlyoutHeader'; await retry.waitFor('Index templates flyout', async () => { return testSubjects.isDisplayed(flyoutTitleSelector); 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 8d32181210fcd..c7dd3acaaf7eb 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 @@ -68,6 +68,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { snapshotRepository: repoName, }); + await retry.waitFor('policy flyout', async () => { + return (await pageObjects.indexLifecycleManagement.flyoutHeaderText()) === policyName; + }); + + await pageObjects.indexLifecycleManagement.closePolicyFlyout(); + await retry.waitFor('navigation back to home page.', async () => { return ( (await pageObjects.indexLifecycleManagement.pageHeaderText()) === diff --git a/x-pack/test/functional/apps/index_lifecycle_management/index.ts b/x-pack/test/functional/apps/index_lifecycle_management/index.ts index 38b5803bd77ef..193d4a026829f 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/index.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/index.ts @@ -11,5 +11,6 @@ export default ({ loadTestFile }: FtrProviderContext) => { describe('Index Lifecycle Management app', function () { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./home_page')); + loadTestFile(require.resolve('./read_only_view')); }); }; diff --git a/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts b/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts new file mode 100644 index 0000000000000..030074a97b4bd --- /dev/null +++ b/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts @@ -0,0 +1,43 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'indexLifecycleManagement']); + const log = getService('log'); + const retry = getService('retry'); + const security = getService('security'); + + describe('Read only view', function () { + before(async () => { + await security.testUser.setRoles(['read_ilm']); + + await pageObjects.common.navigateToApp('indexLifecycleManagement'); + }); + after(async () => { + await security.testUser.restoreDefaults(); + }); + + it('Loads the app', async () => { + await log.debug('Checking for page header'); + const headerText = await pageObjects.indexLifecycleManagement.pageHeaderText(); + expect(headerText).to.be('Index Lifecycle Policies'); + + const createPolicyButtonExists = + await pageObjects.indexLifecycleManagement.createPolicyButtonExists(); + expect(createPolicyButtonExists).to.be(false); + + await pageObjects.indexLifecycleManagement.clickPolicyNameLink(0); + await retry.waitFor('flyout to be visible', async () => { + const flyoutHeader = await pageObjects.indexLifecycleManagement.flyoutHeader(); + return await flyoutHeader.isDisplayed(); + }); + }); + }); +}; diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 033a7faa98303..8d1e875dabccc 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -573,6 +573,20 @@ export default async function ({ readConfigFile }) { ], }, + read_ilm: { + elasticsearch: { + cluster: ['read_ilm'], + }, + kibana: [ + { + feature: { + advancedSettings: ['read'], + }, + spaces: ['default'], + }, + ], + }, + index_management_user: { elasticsearch: { cluster: ['monitor', 'manage_index_templates', 'manage_enrich'], 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 a0061dff067d1..f9c743e8855cf 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 @@ -30,6 +30,9 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider async clickCreatePolicyButton() { return await testSubjects.click('createPolicyButton'); }, + async createPolicyButtonExists() { + return await testSubjects.exists('createPolicyButton'); + }, async fillNewPolicyForm(policy: Policy) { const { policyName, @@ -88,5 +91,19 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider async getPolicyRow(name: string) { return await testSubjects.findAll(`policyTableRow-${name}`); }, + + async flyoutHeaderText() { + return await testSubjects.getVisibleText('policyFlyoutTitle'); + }, + async closePolicyFlyout() { + await testSubjects.click('policyFlyoutCloseButton'); + }, + async flyoutHeader() { + return await testSubjects.find('policyFlyoutTitle'); + }, + async clickPolicyNameLink(index: number) { + const links = await testSubjects.findAll('policyTablePolicyNameLink'); + await links[index].click(); + }, }; }