Skip to content

Commit

Permalink
[Index Management] Keep the filters value in the url for the indices …
Browse files Browse the repository at this point in the history
…list (elastic#174515)

## Summary

Fixes elastic#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 
<img width="916" alt="Screenshot 2024-01-11 at 14 38 22"
src="https://github.com/elastic/kibana/assets/6585477/e7174909-0a68-4041-94a2-2d1646eae0fe">


### 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>
  • Loading branch information
2 people authored and pull[bot] committed Feb 5, 2024
1 parent 417e774 commit 430b462
Show file tree
Hide file tree
Showing 19 changed files with 208 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type TestSubjects =
| 'indexContextMenu'
| 'indexManagementHeaderContent'
| 'indexTable'
| 'indexTableIncludeHiddenIndicesToggle'
| 'checkboxToggles-includeHiddenIndices'
| 'indexTableIndexNameLink'
| 'indicesList'
| 'indicesTab'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const setup = async (httpSetup: HttpSetup): Promise<HomeTestBed> => {
};

const toggleHiddenIndices = async function () {
find('indexTableIncludeHiddenIndicesToggle').simulate('click');
find('checkboxToggles-includeHiddenIndices').simulate('click');
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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']);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -650,11 +650,31 @@ describe('<IndexDetailsPage />', () => {
});
});

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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -34,8 +35,12 @@ export const DetailsPage: FunctionComponent<
const [index, setIndex] = useState<Index | null>();

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) {
Expand Down Expand Up @@ -109,6 +114,7 @@ export const DetailsPage: FunctionComponent<
tab={tab}
fetchIndexDetails={fetchIndexDetails}
history={history}
search={search}
navigateToIndicesList={navigateToIndicesList}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,15 @@ interface Props {
index: Index;
tab: IndexDetailsTabId;
history: RouteComponentProps['history'];
search: string;
fetchIndexDetails: () => Promise<void>;
navigateToIndicesList: () => void;
}
export const DetailsPageContent: FunctionComponent<Props> = ({
index,
tab,
history,
search,
fetchIndexDetails,
navigateToIndicesList,
}) => {
Expand Down Expand Up @@ -111,9 +113,9 @@ export const DetailsPageContent: FunctionComponent<Props> = ({

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<EuiPageHeaderProps['tabs']>(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const AliasesDetails: FunctionComponent<{ aliases: Index['aliases'] }> =
}
const aliasesBadges = aliases.slice(0, MAX_VISIBLE_ALIASES).map((alias) => (
<EuiBadge
key={alias}
css={css`
max-width: 250px;
`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class IndexActionsContextMenu extends Component {
indices,
reloadIndices,
unfreezeIndices,
indicesListURLParams,
} = this.props;
const allOpen = every(indexNames, (indexName) => {
return indexStatusByName[indexName] === INDEX_OPEN;
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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) {
Expand All @@ -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)
);
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand Down Expand Up @@ -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() {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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 });
}
Expand Down Expand Up @@ -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 <DataHealth health={value} />;
Expand All @@ -280,7 +301,7 @@ export class IndexTable extends Component {
<Fragment>
<EuiLink
data-test-subj="indexTableIndexNameLink"
onClick={() => history.push(getIndexDetailsLink(value))}
onClick={() => history.push(getIndexDetailsLink(value, location.search || ''))}
>
{value}
</EuiLink>
Expand Down Expand Up @@ -405,10 +426,16 @@ export class IndexTable extends Component {
<EuiTablePagination
activePage={pager.getCurrentPageIndex()}
itemsPerPage={pager.itemsPerPage}
itemsPerPageOptions={[10, 50, 100]}
itemsPerPageOptions={PAGE_SIZE_OPTIONS}
pageCount={pager.getTotalPages()}
onChangeItemsPerPage={pageSizeChanged}
onChangePage={pageChanged}
onChangeItemsPerPage={(pageSize) => {
this.setURLParam('pageSize', pageSize);
pageSizeChanged(pageSize);
}}
onChangePage={(pageIndex) => {
this.setURLParam('pageIndex', pageIndex);
pageChanged(pageIndex);
}}
/>
);
}
Expand All @@ -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}
/>
</EuiFlexItem>
Expand All @@ -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) {
Expand Down Expand Up @@ -530,21 +560,6 @@ export class IndexTable extends Component {
{extensionsService.toggles.map((toggle) => {
return this.renderToggleControl(toggle);
})}

<EuiFlexItem grow={false}>
<EuiSwitch
id="checkboxShowHiddenIndices"
data-test-subj="indexTableIncludeHiddenIndicesToggle"
checked={includeHiddenIndices}
onChange={(event) => this.setIncludeHiddenParam(event.target.checked)}
label={
<FormattedMessage
id="xpack.idxMgmt.indexTable.hiddenIndicesSwitchLabel"
defaultMessage="Include hidden indices"
/>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiFlexItem>
Expand All @@ -563,6 +578,7 @@ export class IndexTable extends Component {
<IndexActionsContextMenu
indexNames={Object.keys(selectedIndicesMap)}
isOnListView={true}
indicesListURLParams={location.search || ''}
resetSelection={() => {
this.setState({ selectedIndicesMap: {} });
}}
Expand Down
Loading

0 comments on commit 430b462

Please sign in to comment.