From 1e572cfad9014fb0e30aff61429582154072f3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:17:17 +0200 Subject: [PATCH] [ILM] Read only view (#186955) ## Summary Fixes https://github.com/elastic/kibana/issues/184805 This PR adds a flyout to view an ILM policy. With this change, the ILM plugin can be also accessed by users with "read" permission for ILM. To test this PR, create a new role with `read_ilm` Elasticsearch privileges and all Kibana privileges. ### Screenshots Screenshot 2024-09-06 at 17 40 46 Screenshot 2024-09-06 at 17 40 59 https://github.com/user-attachments/assets/01fb445a-120a-489e-8f8d-26375ce391b1 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/policy_flyout.test.tsx.snap | 1027 +++++++++++++++++ .../__jest__/mocks.ts | 97 ++ .../__jest__/policy_flyout.test.tsx | 57 + .../__jest__/policy_table.test.tsx | 40 +- .../common/types/policies.ts | 70 +- .../integration_tests/app/app.helpers.ts | 15 +- .../integration_tests/app/app.test.ts | 12 +- .../public/application/components/index.ts | 8 + .../public/application/constants/policy.ts | 1 - .../public/application/constants/ui_metric.ts | 1 + .../public/application/index.tsx | 3 +- .../application/lib/use_is_read_only.ts | 19 + .../edit_policy/components/edit_warning.tsx | 2 +- .../delete_searchable_snapshot_field.tsx | 12 +- .../components/phases/hot_phase/hot_phase.tsx | 8 +- .../data_tier_allocation_field.tsx | 10 +- .../phases/shared_fields/downsample_field.tsx | 9 +- .../phases/shared_fields/forcemerge_field.tsx | 9 +- .../shared_fields/index_priority_field.tsx | 4 +- .../min_age_field/min_age_field.tsx | 30 +- .../phases/shared_fields/readonly_field.tsx | 10 +- .../phases/shared_fields/replicas_field.tsx | 11 +- .../searchable_snapshot_field.tsx | 8 +- .../phases/shared_fields/shrink_field.tsx | 11 +- .../shared_fields/snapshot_policies_field.tsx | 10 +- .../components/timeline/timeline.tsx | 41 +- .../sections/edit_policy/edit_policy.tsx | 16 +- .../sections/edit_policy/form/deserializer.ts | 1 - .../sections/edit_policy/form/schema.ts | 34 +- .../sections/edit_policy/i18n_texts.ts | 81 +- .../application/sections/edit_policy/types.ts | 2 +- .../components/deprecated_policy_badge.tsx | 32 + .../sections/policy_list/components/index.ts | 13 + .../components/list_action_handler.tsx | 17 +- .../components/managed_policy_badge.tsx | 33 + .../policy_list/components/policy_table.tsx | 168 ++- .../policy_list/policy_flyout/cold_phase.tsx | 37 + .../components/action_description.tsx | 39 + .../components/data_allocation.tsx | 85 ++ .../components/delete_searchable_snapshot.tsx | 27 + .../policy_flyout/components/downsample.tsx | 27 + .../policy_flyout/components/forcemerge.tsx | 38 + .../policy_flyout/components/i18n_texts.ts | 17 + .../policy_flyout/components/index.ts | 19 + .../components/index_priority.tsx | 29 + .../policy_flyout/components/min_age.tsx | 21 + .../policy_flyout/components/readonly.tsx | 23 + .../policy_flyout/components/replicas.tsx | 28 + .../policy_flyout/components/rollover.tsx | 76 ++ .../components/searchable_snapshot.tsx | 35 + .../policy_flyout/components/shrink.tsx | 49 + .../policy_flyout/components/types.ts | 13 + .../components/wait_for_snapshot.tsx | 24 + .../policy_flyout/delete_phase.tsx | 28 + .../policy_flyout/frozen_phase.tsx | 24 + .../policy_list/policy_flyout/hot_phase.tsx | 37 + .../policy_list/policy_flyout/index.ts | 8 + .../policy_flyout/phase_description.tsx | 43 + .../policy_flyout/phase_indicator.tsx | 32 + .../policy_list/policy_flyout/timeline.tsx | 31 + .../policy_flyout/view_policy_flyout.tsx | 204 ++++ .../policy_list/policy_flyout/warm_phase.tsx | 39 + .../sections/policy_list/policy_list.tsx | 37 +- .../policy_list/policy_list_context.tsx | 2 +- .../public/application/services/navigation.ts | 4 + .../public/types.ts | 1 + .../server/plugin.ts | 6 +- .../translations/translations/fr-FR.json | 4 - .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - .../apps/group1/index_lifecycle_management.ts | 37 +- .../index_lifecycle_management/home_page.ts | 6 + .../apps/index_lifecycle_management/index.ts | 1 + .../read_only_view.ts | 43 + x-pack/test/functional/config.base.js | 14 + .../index_lifecycle_management_page.ts | 17 + 76 files changed, 2766 insertions(+), 369 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_flyout.test.tsx.snap create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/mocks.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/policy_flyout.test.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/components/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/lib/use_is_read_only.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/deprecated_policy_badge.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/managed_policy_badge.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/cold_phase.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/action_description.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/data_allocation.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/delete_searchable_snapshot.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/downsample.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/forcemerge.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/i18n_texts.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/index_priority.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/min_age.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/readonly.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/replicas.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/rollover.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/searchable_snapshot.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/shrink.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/types.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/components/wait_for_snapshot.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/delete_phase.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/frozen_phase.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/hot_phase.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_description.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/phase_indicator.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/timeline.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/view_policy_flyout.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/policy_flyout/warm_phase.tsx create mode 100644 x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts 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(); + }, }; }