From 1d8db6098ffa2e5737e4c7c414c8126375aa9d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:04:11 +0100 Subject: [PATCH] [Index Management] Keep the filters value in the url for the indices list (#174515) ## Summary Fixes https://github.com/elastic/kibana/issues/173637 This PR fixes a UX regression introduced in 8.11 by adding a new index details page: the search value is lost when the user clicks the index name and the index details page is opened. When they click the "back to all indices" button, the search in the indices list is cleared. To fix this issue, we need to store the data of the users' search, toggles and pagination before opening the index details page so that when going back to the indices list, these parameters could be restored in the table. This PR adds these parameters to the url when the index page is opened and when navigating back, the indices list is initialized with this data. Since the toggles of the indices list can be added dynamically via the extensions service, there logic for storing this data needs to be dynamic as well. For a cleaner solution, I decided to move the "include hidden indices" toggle to the extensions service as well, since the logic of this toggle is exactly the same as the one for "include rollup indices" toggle for example. This caused a minor change in the UI: the rollup and hidden indices toggles are now displayed in the reversed order. ### Screenshot Screenshot 2024-01-11 at 14 38 22 ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../helpers/test_subjects.ts | 2 +- .../client_integration/home/home.helpers.ts | 2 +- .../home/indices_tab.helpers.ts | 4 +- .../index_details_page.test.tsx | 30 +++++-- .../__jest__/components/index_table.test.js | 2 +- .../index_list/details_page/details_page.tsx | 10 ++- .../details_page/details_page_content.tsx | 6 +- .../details_page_overview/aliases_details.tsx | 1 + .../index_actions_context_menu.js | 17 +++- .../index_list/index_table/index_table.js | 88 +++++++++++-------- .../application/services/routing.test.ts | 43 +++++++++ .../public/application/services/routing.ts | 22 +++-- .../application/store/reducers/table_state.js | 1 + .../application/store/selectors/index.d.ts | 2 +- .../application/store/selectors/index.js | 27 ++---- .../store/selectors/indices_filter.test.ts | 21 ++--- .../public/services/extensions_service.ts | 21 ++++- .../public/extend_index_management/index.ts | 8 +- .../page_objects/index_management_page.ts | 2 +- 19 files changed, 208 insertions(+), 101 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/services/routing.test.ts diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index eaea7f04568d48..6448b9b79d0a5c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -30,7 +30,7 @@ export type TestSubjects = | 'indexContextMenu' | 'indexManagementHeaderContent' | 'indexTable' - | 'indexTableIncludeHiddenIndicesToggle' + | 'checkboxToggles-includeHiddenIndices' | 'indexTableIndexNameLink' | 'indicesList' | 'indicesTab' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts index 25b1f985d441b1..0f1de202f6643f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts @@ -44,7 +44,7 @@ export const setup = async (httpSetup: HttpSetup): Promise => { }; const toggleHiddenIndices = async function () { - find('indexTableIncludeHiddenIndicesToggle').simulate('click'); + find('checkboxToggles-includeHiddenIndices').simulate('click'); }; return { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index cd250756d3dd2c..8a8ae4d620d958 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -74,7 +74,7 @@ export const setup = async ( const clickIncludeHiddenIndicesToggle = () => { const { find } = testBed; - find('indexTableIncludeHiddenIndicesToggle').simulate('click'); + find('checkboxToggles-includeHiddenIndices').simulate('click'); }; const clickManageContextMenuButton = async () => { @@ -88,7 +88,7 @@ export const setup = async ( const getIncludeHiddenIndicesToggleStatus = () => { const { find } = testBed; - const props = find('indexTableIncludeHiddenIndicesToggle').props(); + const props = find('checkboxToggles-includeHiddenIndices').props(); return Boolean(props['aria-checked']); }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index 76a9d9389112ee..a509870ce65203 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -650,11 +650,31 @@ describe('', () => { }); }); - it('navigates back to indices', async () => { - jest.spyOn(testBed.routerMock.history, 'push'); - await testBed.actions.clickBackToIndicesButton(); - expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); - expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices'); + describe('navigates back to the indices list', () => { + it('without indices list params', async () => { + jest.spyOn(testBed.routerMock.history, 'push'); + await testBed.actions.clickBackToIndicesButton(); + expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); + expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices'); + }); + it('with indices list params', async () => { + const filter = 'isFollower:true'; + await act(async () => { + testBed = await setup({ + httpSetup, + initialEntry: `/indices/index_details?indexName=${testIndexName}&filter=${encodeURIComponent( + filter + )}&includeHiddenIndices=true`, + }); + }); + testBed.component.update(); + jest.spyOn(testBed.routerMock.history, 'push'); + await testBed.actions.clickBackToIndicesButton(); + expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1); + expect(testBed.routerMock.history.push).toHaveBeenCalledWith( + `/indices?filter=${encodeURIComponent(filter)}&includeHiddenIndices=true` + ); + }); }); it('renders a link to discover', () => { diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index a3e0dd01f250fc..af3333d3201ef0 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -277,7 +277,7 @@ describe('index table', () => { snapshot(indicesInTable); // Enable "Show hidden indices" - const switchControl = findTestSubject(rendered, 'indexTableIncludeHiddenIndicesToggle'); + const switchControl = findTestSubject(rendered, 'checkboxToggles-includeHiddenIndices'); switchControl.simulate('click'); // We do expect now the `.admin1` and `.admin3` indices to be in the list diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx index 7872f2eacda78b..52572faea21ada 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx @@ -6,6 +6,7 @@ */ import React, { useCallback, useEffect, useMemo, useState, FunctionComponent } from 'react'; +import qs from 'query-string'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPageTemplate, EuiText, EuiCode } from '@elastic/eui'; @@ -34,8 +35,12 @@ export const DetailsPage: FunctionComponent< const [index, setIndex] = useState(); const navigateToIndicesList = useCallback(() => { - history.push(`/${Section.Indices}`); - }, [history]); + const indicesListParams = qs.parse(search); + delete indicesListParams.indexName; + delete indicesListParams.tab; + const paramsString = qs.stringify(indicesListParams); + history.push(`/${Section.Indices}${paramsString ? '?' : ''}${paramsString}`); + }, [history, search]); const fetchIndexDetails = useCallback(async () => { if (indexName) { @@ -109,6 +114,7 @@ export const DetailsPage: FunctionComponent< tab={tab} fetchIndexDetails={fetchIndexDetails} history={history} + search={search} navigateToIndicesList={navigateToIndicesList} /> ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx index 9b2beafd31cf55..447c3ff330bac4 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx @@ -77,6 +77,7 @@ interface Props { index: Index; tab: IndexDetailsTabId; history: RouteComponentProps['history']; + search: string; fetchIndexDetails: () => Promise; navigateToIndicesList: () => void; } @@ -84,6 +85,7 @@ export const DetailsPageContent: FunctionComponent = ({ index, tab, history, + search, fetchIndexDetails, navigateToIndicesList, }) => { @@ -111,9 +113,9 @@ export const DetailsPageContent: FunctionComponent = ({ const onSectionChange = useCallback( (newSection: IndexDetailsTabId) => { - return history.push(getIndexDetailsLink(index.name, newSection)); + return history.push(getIndexDetailsLink(index.name, search, newSection)); }, - [history, index] + [history, index.name, search] ); const headerTabs = useMemo(() => { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx index 8ea5a1fa491ce2..934c7c31cdbfab 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx @@ -37,6 +37,7 @@ export const AliasesDetails: FunctionComponent<{ aliases: Index['aliases'] }> = } const aliasesBadges = aliases.slice(0, MAX_VISIBLE_ALIASES).map((alias) => ( { return indexStatusByName[indexName] === INDEX_OPEN; @@ -82,7 +83,9 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index overview', }), onClick: () => { - history.push(getIndexDetailsLink(indexNames[0], IndexDetailsSection.Overview)); + history.push( + getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Overview) + ); }, }); items.push({ @@ -91,7 +94,9 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index settings', }), onClick: () => { - history.push(getIndexDetailsLink(indexNames[0], IndexDetailsSection.Settings)); + history.push( + getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Settings) + ); }, }); items.push({ @@ -100,7 +105,9 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index mapping', }), onClick: () => { - history.push(getIndexDetailsLink(indexNames[0], IndexDetailsSection.Mappings)); + history.push( + getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Mappings) + ); }, }); if (allOpen && enableIndexActions) { @@ -110,7 +117,9 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index stats', }), onClick: () => { - history.push(getIndexDetailsLink(indexNames[0], IndexDetailsSection.Stats)); + history.push( + getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Stats) + ); }, }); } diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 2c26a91a5108f6..137c4b8ca67e08 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -49,6 +49,8 @@ import { NoMatch, DataHealth } from '../../../../components'; import { IndexActionsContextMenu } from '../index_actions_context_menu'; import { CreateIndexButton } from '../create_index/create_index_button'; +const PAGE_SIZE_OPTIONS = [10, 50, 100]; + const getHeaders = ({ showIndexStats }) => { const headers = {}; @@ -118,18 +120,32 @@ export class IndexTable extends Component { componentDidMount() { this.props.loadIndices(); - const { location, filterChanged } = this.props; - const { filter } = qs.parse((location && location.search) || ''); - if (filter) { - const decodedFilter = attemptToURIDecode(filter); + const { filterChanged, pageSizeChanged, pageChanged, toggleNameToVisibleMap, toggleChanged } = + this.props; + const { filter, pageSize, pageIndex, ...rest } = this.readURLParams(); + + if (filter) { try { - const filter = EuiSearchBar.Query.parse(decodedFilter); - filterChanged(filter); + const parsedFilter = EuiSearchBar.Query.parse(filter); + filterChanged(parsedFilter); } catch (e) { this.setState({ filterError: e }); } } + if (pageSize && PAGE_SIZE_OPTIONS.includes(pageSize)) { + pageSizeChanged(pageSize); + } + if (pageIndex && pageIndex > -1) { + pageChanged(pageIndex); + } + const toggleParams = Object.keys(rest); + const toggles = Object.keys(toggleNameToVisibleMap); + for (const toggleParam of toggleParams) { + if (toggles.includes(toggleParam)) { + toggleChanged(toggleParam, rest[toggleParam] === 'true'); + } + } } componentWillUnmount() { @@ -142,21 +158,25 @@ export class IndexTable extends Component { readURLParams() { const { location } = this.props; - const { includeHiddenIndices } = qs.parse((location && location.search) || ''); + const { filter, pageSize, pageIndex, ...rest } = qs.parse((location && location.search) || ''); return { - includeHiddenIndices: includeHiddenIndices === 'true', + filter: filter ? attemptToURIDecode(String(filter)) : undefined, + pageSize: pageSize ? Number(String(pageSize)) : undefined, + pageIndex: pageIndex ? Number(String(pageIndex)) : undefined, + ...rest, }; } - setIncludeHiddenParam(hidden) { - const { pathname, search } = this.props.location; + setURLParam(paramName, value) { + const { location, history } = this.props; + const { pathname, search } = location; const params = qs.parse(search); - if (hidden) { - params.includeHiddenIndices = 'true'; + if (value) { + params[paramName] = value; } else { - delete params.includeHiddenIndices; + delete params[paramName]; } - this.props.history.push(pathname + '?' + qs.stringify(params)); + history.push(pathname + '?' + qs.stringify(params)); } onSort = (column) => { @@ -196,6 +216,7 @@ export class IndexTable extends Component { if (error) { this.setState({ filterError: error }); } else { + this.setURLParam('filter', encodeURIComponent(query.text)); this.props.filterChanged(query); this.setState({ filterError: null }); } @@ -271,7 +292,7 @@ export class IndexTable extends Component { } buildRowCell(fieldName, value, index, appServices) { - const { filterChanged, history } = this.props; + const { filterChanged, history, location } = this.props; if (fieldName === 'health') { return ; @@ -280,7 +301,7 @@ export class IndexTable extends Component { history.push(getIndexDetailsLink(value))} + onClick={() => history.push(getIndexDetailsLink(value, location.search || ''))} > {value} @@ -405,10 +426,16 @@ export class IndexTable extends Component { { + this.setURLParam('pageSize', pageSize); + pageSizeChanged(pageSize); + }} + onChangePage={(pageIndex) => { + this.setURLParam('pageIndex', pageIndex); + pageChanged(pageIndex); + }} /> ); } @@ -425,7 +452,10 @@ export class IndexTable extends Component { id={`checkboxToggles-${name}`} data-test-subj={`checkboxToggles-${name}`} checked={toggleNameToVisibleMap[name]} - onChange={(event) => toggleChanged(name, event.target.checked)} + onChange={(event) => { + this.setURLParam(name, event.target.checked); + toggleChanged(name, event.target.checked); + }} label={label} /> @@ -442,9 +472,9 @@ export class IndexTable extends Component { indicesError, allIndices, pager, + location, } = this.props; - const { includeHiddenIndices } = this.readURLParams(); const hasContent = !indicesLoading && !indicesError; if (!hasContent) { @@ -530,21 +560,6 @@ export class IndexTable extends Component { {extensionsService.toggles.map((toggle) => { return this.renderToggleControl(toggle); })} - - - this.setIncludeHiddenParam(event.target.checked)} - label={ - - } - /> - )} @@ -563,6 +578,7 @@ export class IndexTable extends Component { { this.setState({ selectedIndicesMap: {} }); }} diff --git a/x-pack/plugins/index_management/public/application/services/routing.test.ts b/x-pack/plugins/index_management/public/application/services/routing.test.ts new file mode 100644 index 00000000000000..24500cb6059bf4 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/services/routing.test.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 { getIndexDetailsLink, getIndexListUri } from './routing'; + +describe('routing', () => { + describe('index details link', () => { + it('adds the index name to the url', () => { + const indexName = 'testIndex'; + const url = getIndexDetailsLink(indexName, ''); + expect(url).toContain(`indexName=${indexName}`); + }); + + it('adds the indices table parameters to the url', () => { + const filter = 'isFollower:true'; + const url = getIndexDetailsLink('testIndex', `?filter=${encodeURIComponent(filter)}`); + expect(url).toContain(`&filter=${encodeURIComponent(filter)}`); + }); + + it('adds an optional index details tab to the url', () => { + const tab = 'dynamic-tab'; + const url = getIndexDetailsLink('testIndex', '', tab); + expect(url).toContain(`tab=${tab}`); + }); + }); + + describe('indices list link', () => { + it('adds filter to the url', () => { + const filter = 'isFollower:true'; + const url = getIndexListUri(filter); + expect(url).toContain(`?filter=${encodeURIComponent(filter)}`); + }); + + it('adds includeHiddenIndices param to the url', () => { + const url = getIndexListUri('', true); + expect(url).toContain(`?includeHiddenIndices=true`); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts index 02e1410e9551c4..a2d4a030135562 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -35,27 +35,35 @@ export const getTemplateCloneLink = (name: string, isLegacy?: boolean) => { }; export const getIndexListUri = (filter?: string, includeHiddenIndices?: boolean) => { + let url = `/${Section.Indices}`; const hiddenIndicesParam = typeof includeHiddenIndices !== 'undefined' ? includeHiddenIndices : false; - if (filter) { + if (hiddenIndicesParam) { + url = `${url}?includeHiddenIndices=${hiddenIndicesParam}`; + } + if (filter && filter !== 'undefined') { // React router tries to decode url params but it can't because the browser partially // decodes them. So we have to encode both the URL and the filter to get it all to // work correctly for filters with URL unsafe characters in them. - return encodeURI( - `/indices?includeHiddenIndices=${hiddenIndicesParam}&filter=${encodeURIComponent(filter)}` - ); + url = `${url}${hiddenIndicesParam ? '&' : '?'}filter=${encodeURIComponent(filter)}`; } - // If no filter, URI is already safe so no need to encode. - return '/indices'; + return url; }; export const getDataStreamDetailsLink = (name: string) => { return encodeURI(`/data_streams/${encodeURIComponent(name)}`); }; -export const getIndexDetailsLink = (indexName: string, tab?: IndexDetailsTabId) => { +export const getIndexDetailsLink = ( + indexName: string, + indicesListURLParams: string, + tab?: IndexDetailsTabId +) => { let link = `/${Section.Indices}/index_details?indexName=${encodeURIComponent(indexName)}`; + if (indicesListURLParams) { + link = `${link}&${indicesListURLParams.replace('?', '')}`; + } if (tab) { link = `${link}&tab=${tab}`; } diff --git a/x-pack/plugins/index_management/public/application/store/reducers/table_state.js b/x-pack/plugins/index_management/public/application/store/reducers/table_state.js index c07ed875ee6e78..e5a5627300fbf8 100644 --- a/x-pack/plugins/index_management/public/application/store/reducers/table_state.js +++ b/x-pack/plugins/index_management/public/application/store/reducers/table_state.js @@ -20,6 +20,7 @@ export const defaultTableState = { currentPage: 0, sortField: 'index.name', isSortAscending: true, + toggleNameToVisibleMap: {}, }; export const tableState = handleActions( diff --git a/x-pack/plugins/index_management/public/application/store/selectors/index.d.ts b/x-pack/plugins/index_management/public/application/store/selectors/index.d.ts index 65d72ef26cfdad..e33fff1864a64c 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/index.d.ts +++ b/x-pack/plugins/index_management/public/application/store/selectors/index.d.ts @@ -9,4 +9,4 @@ import { ExtensionsService } from '../../../services'; export declare function setExtensionsService(extensionsService: ExtensionsService): any; -export const getFilteredIndices: (state: any, props: any) => any; +export const getFilteredIndices: (state: any) => any; diff --git a/x-pack/plugins/index_management/public/application/store/selectors/index.js b/x-pack/plugins/index_management/public/application/store/selectors/index.js index 57ba47205e0123..6ed09d8234fa44 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/index.js +++ b/x-pack/plugins/index_management/public/application/store/selectors/index.js @@ -8,9 +8,7 @@ import { Pager, EuiSearchBar } from '@elastic/eui'; import { createSelector } from 'reselect'; -import * as qs from 'query-string'; import { indexStatusLabels } from '../../lib/index_status_labels'; -import { isHiddenIndex } from '../../lib/indices'; import { sortTable } from '../../services'; import { extensionsService } from './extension_service'; @@ -50,16 +48,15 @@ const filterByToggles = (indices, toggleNameToVisibleMap) => { if (!toggleNames.length) { return indices; } - // An index is visible if ANY applicable toggle is visible. return indices.filter((index) => { - return toggleNames.some((toggleName) => { - if (!togglesByName[toggleName].matchIndex(index)) { - return true; + return toggleNames.every((toggleName) => { + // if an index matches a toggle, it's only shown if the toggle is set to "enabled" + // for example, a hidden index is only shown when the "include hidden" toggle is "enabled" + if (togglesByName[toggleName].matchIndex(index)) { + return toggleNameToVisibleMap[toggleName] === true; } - - const isVisible = toggleNameToVisibleMap[toggleName] === true; - - return isVisible; + // otherwise the index is shown by default + return true; }); }); }; @@ -68,17 +65,11 @@ export const getFilteredIndices = createSelector( getIndices, getAllIds, getTableState, - getTableLocationProp, - (indices, allIds, tableState, tableLocation) => { + (indices, allIds, tableState) => { let indexArray = allIds.map((indexName) => indices[indexName]); indexArray = filterByToggles(indexArray, tableState.toggleNameToVisibleMap); - const { includeHiddenIndices: includeHiddenParam } = qs.parse(tableLocation.search); - const includeHidden = includeHiddenParam === 'true'; - const filteredIndices = includeHidden - ? indexArray - : indexArray.filter((index) => !isHiddenIndex(index)); const filter = tableState.filter || EuiSearchBar.Query.MATCH_ALL; - return EuiSearchBar.Query.execute(filter, filteredIndices, { + return EuiSearchBar.Query.execute(filter, indexArray, { defaultFields: defaultFilterFields, }); } diff --git a/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts b/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts index bdb531e41abb22..adb078674755de 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts +++ b/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts @@ -4,17 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import SemVer from 'semver/classes/semver'; -import { MAJOR_VERSION } from '../../../../common'; import { ExtensionsService } from '../../../services'; import { getFilteredIndices } from '.'; // @ts-ignore import { defaultTableState } from '../reducers/table_state'; import { setExtensionsService } from './extension_service'; -const kibanaVersion = new SemVer(MAJOR_VERSION); - describe('getFilteredIndices selector', () => { let extensionService: ExtensionsService; beforeAll(() => { @@ -37,19 +33,20 @@ describe('getFilteredIndices selector', () => { }; it('filters out hidden indices', () => { - let expected = [{ name: 'index2', hidden: false }, { name: 'index3' }, { name: '.index4' }]; - - if (kibanaVersion.major < 8) { - // In 7.x index name starting with a dot are considered hidden indices - expected = [{ name: 'index2', hidden: false }, { name: 'index3' }]; - } + const expected = [{ name: 'index2', hidden: false }, { name: 'index3' }, { name: '.index4' }]; - expect(getFilteredIndices(state, { location: { search: '' } })).toEqual(expected); + expect(getFilteredIndices(state)).toEqual(expected); }); it('includes hidden indices', () => { expect( - getFilteredIndices(state, { location: { search: '?includeHiddenIndices=true' } }) + getFilteredIndices({ + ...state, + tableState: { + ...defaultTableState, + toggleNameToVisibleMap: { includeHiddenIndices: true }, + }, + }) ).toEqual([ { name: 'index1', hidden: true }, { name: 'index2', hidden: false }, diff --git a/x-pack/plugins/index_management/public/services/extensions_service.ts b/x-pack/plugins/index_management/public/services/extensions_service.ts index 955b92ab160e53..98e707133c4d10 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -19,6 +19,11 @@ export interface IndexContent { }) => ReturnType; } +export interface IndexToggle { + matchIndex: (index: Index) => boolean; + label: string; + name: string; +} export interface IndexBadge { matchIndex: (index: Index) => boolean; label: string; @@ -43,7 +48,7 @@ export interface ExtensionsSetup { // adds a badge to the index name addBadge(badge: IndexBadge): void; // adds a toggle to the indices list - addToggle(toggle: any): void; + addToggle(toggle: IndexToggle): void; // set the content to render when the indices list is empty setEmptyListContent(content: EmptyListContent): void; // adds a tab to the index details page @@ -60,7 +65,7 @@ export class ExtensionsService { private _filters: any[] = []; private _badges: IndexBadge[] = [ { - matchIndex: (index: { isFrozen: boolean }) => { + matchIndex: (index) => { return index.isFrozen; }, label: i18n.translate('xpack.idxMgmt.frozenBadgeLabel', { @@ -70,7 +75,17 @@ export class ExtensionsService { color: 'primary', }, ]; - private _toggles: any[] = []; + private _toggles: IndexToggle[] = [ + { + matchIndex: (index) => { + return index.hidden; + }, + label: i18n.translate('xpack.idxMgmt.indexTable.hiddenIndicesSwitchLabel', { + defaultMessage: 'Include hidden indices', + }), + name: 'includeHiddenIndices', + }, + ]; private _emptyListContent: EmptyListContent | null = null; private _indexDetailsTabs: IndexDetailsTab[] = []; private _indexOverviewContent: IndexContent | null = null; diff --git a/x-pack/plugins/rollup/public/extend_index_management/index.ts b/x-pack/plugins/rollup/public/extend_index_management/index.ts index b285db2b9ca767..116d7b38996f85 100644 --- a/x-pack/plugins/rollup/public/extend_index_management/index.ts +++ b/x-pack/plugins/rollup/public/extend_index_management/index.ts @@ -7,12 +7,10 @@ import { i18n } from '@kbn/i18n'; import { Index } from '@kbn/index-management-plugin/common'; -import { get } from 'lodash'; -const propertyPath = 'isRollupIndex'; export const rollupToggleExtension = { - matchIndex: (index: { isRollupIndex: boolean }) => { - return get(index, propertyPath); + matchIndex: (index: Index) => { + return Boolean(index.isRollupIndex); }, label: i18n.translate('xpack.rollupJobs.indexMgmtToggle.toggleLabel', { defaultMessage: 'Include rollup indices', @@ -22,7 +20,7 @@ export const rollupToggleExtension = { export const rollupBadgeExtension = { matchIndex: (index: Index) => { - return !!get(index, propertyPath); + return Boolean(index.isRollupIndex); }, label: i18n.translate('xpack.rollupJobs.indexMgmtBadge.rollupLabel', { defaultMessage: 'Rollup', diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts index 19eb3f2824f3af..718dac14221aca 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -23,7 +23,7 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) return await testSubjects.find('reloadIndicesButton'); }, async toggleHiddenIndices() { - await testSubjects.click('indexTableIncludeHiddenIndicesToggle'); + await testSubjects.click('checkboxToggles-includeHiddenIndices'); }, async clickEnrichPolicyAt(indexOfRow: number): Promise {