Skip to content

Commit

Permalink
Persistent list filters (#12229)
Browse files Browse the repository at this point in the history
* add PersistentFilters component

* add PersistentFilters test

* add persistent filters to all list pages

* update tests

* clear sessionStorage on logout

* fix persistent filter on wfjt detail; cleanup
  • Loading branch information
keithjgrant committed Jun 6, 2022
1 parent faa5df1 commit fdd5607
Show file tree
Hide file tree
Showing 45 changed files with 277 additions and 51 deletions.
17 changes: 13 additions & 4 deletions awx/ui/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"babelOptions": {
"presets": ["@babel/preset-react"]
}
}
},
"plugins": ["react-hooks", "jsx-a11y", "i18next", "@babel"],
"extends": [
Expand Down Expand Up @@ -96,9 +96,18 @@
"modifier",
"data-cy",
"fieldName",
"splitButtonVariant"
"splitButtonVariant",
"pageKey"
],
"ignore": [
"Ansible",
"Tower",
"JSON",
"YAML",
"lg",
"hh:mm AM/PM",
"Twilio"
],
"ignore": ["Ansible", "Tower", "JSON", "YAML", "lg", "hh:mm AM/PM", "Twilio"],
"ignoreComponent": [
"AboutModal",
"code",
Expand Down Expand Up @@ -139,7 +148,7 @@
"object-curly-newline": "off",
"no-trailing-spaces": ["error"],
"no-unused-expressions": ["error", { "allowShortCircuit": true }],
"react/jsx-props-no-spreading":["off"],
"react/jsx-props-no-spreading": ["off"],
"react/prefer-stateless-function": "off",
"react/prop-types": "off",
"react/sort-comp": ["error", {}],
Expand Down
33 changes: 33 additions & 0 deletions awx/ui/src/components/PersistentFilters/PersistentFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useEffect } from 'react';
import { useLocation, useHistory } from 'react-router';
import { PERSISTENT_FILTER_KEY } from '../../constants';

export default function PersistentFilters({ pageKey, children }) {
const location = useLocation();
const history = useHistory();

useEffect(() => {
if (!location.search.includes('restoreFilters=true')) {
return;
}

const filterString = sessionStorage.getItem(PERSISTENT_FILTER_KEY);
const filter = filterString ? JSON.parse(filterString) : { qs: '' };

if (filter.pageKey === pageKey) {
history.replace(`${location.pathname}${filter.qs}`);
} else {
history.replace(location.pathname);
}
}, [history, location, pageKey]);

useEffect(() => {
const filter = {
pageKey,
qs: location.search,
};
sessionStorage.setItem(PERSISTENT_FILTER_KEY, JSON.stringify(filter));
}, [location.search, pageKey]);

return children;
}
111 changes: 111 additions & 0 deletions awx/ui/src/components/PersistentFilters/PersistentFilters.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { Router, Route } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import PersistentFilters from './PersistentFilters';

const KEY = 'awx-persistent-filter';

describe('PersistentFilters', () => {
test('should initialize filter in sessionStorage', () => {
expect(sessionStorage.getItem(KEY)).toEqual(null);
const history = createMemoryHistory({
initialEntries: ['/templates'],
});
render(
<Router history={history}>
<PersistentFilters pageKey="templates">test</PersistentFilters>
</Router>
);

expect(JSON.parse(sessionStorage.getItem(KEY))).toEqual({
pageKey: 'templates',
qs: '',
});
});

test('should restore filters from sessionStorage', () => {
expect(
sessionStorage.setItem(
KEY,
JSON.stringify({
pageKey: 'templates',
qs: '?page=2&name=foo',
})
)
);
const history = createMemoryHistory({
initialEntries: ['/templates?restoreFilters=true'],
});
render(
<Router history={history}>
<PersistentFilters pageKey="templates">test</PersistentFilters>
</Router>
);

expect(history.location.search).toEqual('?page=2&name=foo');
});

test('should not restore filters without restoreFilters query param', () => {
expect(
sessionStorage.setItem(
KEY,
JSON.stringify({
pageKey: 'templates',
qs: '?page=2&name=foo',
})
)
);
const history = createMemoryHistory({
initialEntries: ['/templates'],
});
render(
<Router history={history}>
<PersistentFilters pageKey="templates">test</PersistentFilters>
</Router>
);

expect(history.location.search).toEqual('');
});

test("should not restore filters if page key doesn't match", () => {
expect(
sessionStorage.setItem(
KEY,
JSON.stringify({
pageKey: 'projects',
qs: '?page=2&name=foo',
})
)
);
const history = createMemoryHistory({
initialEntries: ['/templates?restoreFilters=true'],
});
render(
<Router history={history}>
<PersistentFilters pageKey="templates">test</PersistentFilters>
</Router>
);

expect(history.location.search).toEqual('');
});

test('should update stored filters when qs changes', async () => {
const history = createMemoryHistory({
initialEntries: ['/templates'],
});
render(
<Router history={history}>
<PersistentFilters pageKey="templates">test</PersistentFilters>
</Router>
);

history.push('/templates?page=3');
await waitFor(() => true);

expect(JSON.parse(sessionStorage.getItem(KEY))).toEqual({
pageKey: 'templates',
qs: '?page=3',
});
});
});
1 change: 1 addition & 0 deletions awx/ui/src/components/PersistentFilters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './PersistentFilters';
8 changes: 6 additions & 2 deletions awx/ui/src/components/RoutedTabs/RoutedTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ function RoutedTabs({ tabsArray }) {
const handleTabSelect = (event, eventKey) => {
const match = tabsArray.find((tab) => tab.id === eventKey);
if (match) {
history.push(match.link);
event.preventDefault();
const link = match.isBackButton
? `${match.link}?restoreFilters=true`
: match.link;
history.push(link);
}
};

Expand All @@ -39,7 +43,7 @@ function RoutedTabs({ tabsArray }) {
aria-label={typeof tab.name === 'string' ? tab.name : null}
eventKey={tab.id}
key={tab.id}
link={tab.link}
href={`#${tab.link}`}
title={<TabTitleText>{tab.name}</TabTitleText>}
aria-controls=""
ouiaId={`${tab.name}-tab`}
Expand Down
7 changes: 6 additions & 1 deletion awx/ui/src/components/RoutedTabs/RoutedTabs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ describe('<RoutedTabs />', () => {
});

test('should update history when new tab selected', async () => {
wrapper.find('Tabs').invoke('onSelect')({}, 2);
wrapper.find('Tabs').invoke('onSelect')(
{
preventDefault: () => {},
},
2
);
wrapper.update();

expect(history.location.pathname).toEqual('/organizations/19/access');
Expand Down
9 changes: 5 additions & 4 deletions awx/ui/src/components/Schedule/Schedule.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ describe('<Schedule />', () => {
});

test('expect all tabs to exist, including Back to Schedules', async () => {
expect(
wrapper.find('button[link="/templates/job_template/1/schedules"]').length
).toBe(1);
expect(wrapper.find('button[aria-label="Details"]').length).toBe(1);
const routedTabs = wrapper.find('RoutedTabs');
const tabs = routedTabs.prop('tabsArray');

expect(tabs[0].link).toEqual('/templates/job_template/1/schedules');
expect(tabs[1].name).toEqual('Details');
});
});
1 change: 1 addition & 0 deletions awx/ui/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export const JOB_TYPE_URL_SEGMENTS = {

export const SESSION_TIMEOUT_KEY = 'awx-session-timeout';
export const SESSION_REDIRECT_URL = 'awx-redirect-url';
export const PERSISTENT_FILTER_KEY = 'awx-persistent-filter';
1 change: 1 addition & 0 deletions awx/ui/src/contexts/Session.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function SessionProvider({ children }) {
if (!isSessionExpired.current) {
setAuthRedirectTo('/logout');
}
sessionStorage.clear();
await RootAPI.logout();
setSessionTimeout(0);
setSessionCountdown(0);
Expand Down
1 change: 1 addition & 0 deletions awx/ui/src/screens/Application/Application/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function Application({ setBreadcrumb }) {
),
link: '/applications',
id: 0,
isBackButton: true,
},
{ name: t`Details`, link: `/applications/${id}/details`, id: 1 },
{ name: t`Tokens`, link: `/applications/${id}/tokens`, id: 2 },
Expand Down
5 changes: 4 additions & 1 deletion awx/ui/src/screens/Application/Applications.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@patternfly/react-core';
import ScreenHeader from 'components/ScreenHeader';
import { Detail, DetailList } from 'components/DetailList';
import PersistentFilters from 'components/PersistentFilters';
import ApplicationsList from './ApplicationsList';
import ApplicationAdd from './ApplicationAdd';
import Application from './Application';
Expand Down Expand Up @@ -56,7 +57,9 @@ function Applications() {
<Application setBreadcrumb={buildBreadcrumbConfig} />
</Route>
<Route path="/applications">
<ApplicationsList />
<PersistentFilters pageKey="applications">
<ApplicationsList />
</PersistentFilters>
</Route>
</Switch>
{applicationModalSource && (
Expand Down
1 change: 1 addition & 0 deletions awx/ui/src/screens/Credential/Credential.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function Credential({ setBreadcrumb }) {
),
link: `/credentials`,
id: 99,
isBackButton: true,
},
{ name: t`Details`, link: `/credentials/${id}/details`, id: 0 },
{
Expand Down
5 changes: 4 additions & 1 deletion awx/ui/src/screens/Credential/Credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Route, Switch } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import Credential from './Credential';
import CredentialAdd from './CredentialAdd';
import { CredentialList } from './CredentialList';
Expand Down Expand Up @@ -44,7 +45,9 @@ function Credentials() {
<Credential setBreadcrumb={buildBreadcrumbConfig} />
</Route>
<Route path="/credentials">
<CredentialList />
<PersistentFilters pageKey="credentials">
<CredentialList />
</PersistentFilters>
</Route>
</Switch>
</>
Expand Down
1 change: 1 addition & 0 deletions awx/ui/src/screens/CredentialType/CredentialType.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function CredentialType({ setBreadcrumb }) {
),
link: '/credential_types',
id: 99,
isBackButton: true,
},
{
name: t`Details`,
Expand Down
6 changes: 4 additions & 2 deletions awx/ui/src/screens/CredentialType/CredentialTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react';

import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom';

import PersistentFilters from 'components/PersistentFilters';
import ScreenHeader from 'components/ScreenHeader';
import CredentialTypeAdd from './CredentialTypeAdd';
import CredentialTypeList from './CredentialTypeList';
Expand Down Expand Up @@ -40,7 +40,9 @@ function CredentialTypes() {
<CredentialType setBreadcrumb={buildBreadcrumbConfig} />
</Route>
<Route path="/credential_types">
<CredentialTypeList />
<PersistentFilters pageKey="credentialTypes">
<CredentialTypeList />
</PersistentFilters>
</Route>
</Switch>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function ExecutionEnvironment({ setBreadcrumb }) {
),
link: '/execution_environments',
id: 99,
isBackButton: true,
},
{
name: t`Details`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react';

import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom';

import PersistentFilters from 'components/PersistentFilters';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import ExecutionEnvironment from './ExecutionEnvironment';
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
Expand Down Expand Up @@ -40,7 +40,9 @@ function ExecutionEnvironments() {
<ExecutionEnvironment setBreadcrumb={buildBreadcrumbConfig} />
</Route>
<Route path="/execution_environments">
<ExecutionEnvironmentList />
<PersistentFilters pageKey="executionEnvironments">
<ExecutionEnvironmentList />
</PersistentFilters>
</Route>
</Switch>
</>
Expand Down
1 change: 1 addition & 0 deletions awx/ui/src/screens/Host/Host.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function Host({ setBreadcrumb }) {
),
link: `/hosts`,
id: 99,
isBackButton: true,
},
{
name: t`Details`,
Expand Down
7 changes: 4 additions & 3 deletions awx/ui/src/screens/Host/Hosts.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React, { useState, useCallback } from 'react';
import { Route, Switch } from 'react-router-dom';

import { t } from '@lingui/macro';

import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';

import PersistentFilters from 'components/PersistentFilters';
import HostList from './HostList';
import HostAdd from './HostAdd';
import Host from './Host';
Expand Down Expand Up @@ -47,7 +46,9 @@ function Hosts() {
</Config>
</Route>
<Route path="/hosts">
<HostList />
<PersistentFilters pageKey="hosts">
<HostList />
</PersistentFilters>
</Route>
</Switch>
</>
Expand Down
Loading

0 comments on commit fdd5607

Please sign in to comment.