Persistent list filters (#12229)

* 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
This commit is contained in:
Keith Grant
2022-06-06 13:56:45 -07:00
committed by GitHub
parent faa5df19ca
commit fdd560747d
45 changed files with 277 additions and 51 deletions

View File

@@ -11,7 +11,7 @@
}, },
"babelOptions": { "babelOptions": {
"presets": ["@babel/preset-react"] "presets": ["@babel/preset-react"]
} }
}, },
"plugins": ["react-hooks", "jsx-a11y", "i18next", "@babel"], "plugins": ["react-hooks", "jsx-a11y", "i18next", "@babel"],
"extends": [ "extends": [
@@ -96,9 +96,18 @@
"modifier", "modifier",
"data-cy", "data-cy",
"fieldName", "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": [ "ignoreComponent": [
"AboutModal", "AboutModal",
"code", "code",
@@ -139,7 +148,7 @@
"object-curly-newline": "off", "object-curly-newline": "off",
"no-trailing-spaces": ["error"], "no-trailing-spaces": ["error"],
"no-unused-expressions": ["error", { "allowShortCircuit": true }], "no-unused-expressions": ["error", { "allowShortCircuit": true }],
"react/jsx-props-no-spreading":["off"], "react/jsx-props-no-spreading": ["off"],
"react/prefer-stateless-function": "off", "react/prefer-stateless-function": "off",
"react/prop-types": "off", "react/prop-types": "off",
"react/sort-comp": ["error", {}], "react/sort-comp": ["error", {}],

View File

@@ -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;
}

View File

@@ -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',
});
});
});

View File

@@ -0,0 +1 @@
export { default } from './PersistentFilters';

View File

@@ -24,7 +24,11 @@ function RoutedTabs({ tabsArray }) {
const handleTabSelect = (event, eventKey) => { const handleTabSelect = (event, eventKey) => {
const match = tabsArray.find((tab) => tab.id === eventKey); const match = tabsArray.find((tab) => tab.id === eventKey);
if (match) { if (match) {
history.push(match.link); event.preventDefault();
const link = match.isBackButton
? `${match.link}?restoreFilters=true`
: match.link;
history.push(link);
} }
}; };
@@ -39,7 +43,7 @@ function RoutedTabs({ tabsArray }) {
aria-label={typeof tab.name === 'string' ? tab.name : null} aria-label={typeof tab.name === 'string' ? tab.name : null}
eventKey={tab.id} eventKey={tab.id}
key={tab.id} key={tab.id}
link={tab.link} href={`#${tab.link}`}
title={<TabTitleText>{tab.name}</TabTitleText>} title={<TabTitleText>{tab.name}</TabTitleText>}
aria-controls="" aria-controls=""
ouiaId={`${tab.name}-tab`} ouiaId={`${tab.name}-tab`}

View File

@@ -37,7 +37,12 @@ describe('<RoutedTabs />', () => {
}); });
test('should update history when new tab selected', async () => { test('should update history when new tab selected', async () => {
wrapper.find('Tabs').invoke('onSelect')({}, 2); wrapper.find('Tabs').invoke('onSelect')(
{
preventDefault: () => {},
},
2
);
wrapper.update(); wrapper.update();
expect(history.location.pathname).toEqual('/organizations/19/access'); expect(history.location.pathname).toEqual('/organizations/19/access');

View File

@@ -119,9 +119,10 @@ describe('<Schedule />', () => {
}); });
test('expect all tabs to exist, including Back to Schedules', async () => { test('expect all tabs to exist, including Back to Schedules', async () => {
expect( const routedTabs = wrapper.find('RoutedTabs');
wrapper.find('button[link="/templates/job_template/1/schedules"]').length const tabs = routedTabs.prop('tabsArray');
).toBe(1);
expect(wrapper.find('button[aria-label="Details"]').length).toBe(1); expect(tabs[0].link).toEqual('/templates/job_template/1/schedules');
expect(tabs[1].name).toEqual('Details');
}); });
}); });

View File

@@ -10,3 +10,4 @@ export const JOB_TYPE_URL_SEGMENTS = {
export const SESSION_TIMEOUT_KEY = 'awx-session-timeout'; export const SESSION_TIMEOUT_KEY = 'awx-session-timeout';
export const SESSION_REDIRECT_URL = 'awx-redirect-url'; export const SESSION_REDIRECT_URL = 'awx-redirect-url';
export const PERSISTENT_FILTER_KEY = 'awx-persistent-filter';

View File

@@ -102,6 +102,7 @@ function SessionProvider({ children }) {
if (!isSessionExpired.current) { if (!isSessionExpired.current) {
setAuthRedirectTo('/logout'); setAuthRedirectTo('/logout');
} }
sessionStorage.clear();
await RootAPI.logout(); await RootAPI.logout();
setSessionTimeout(0); setSessionTimeout(0);
setSessionCountdown(0); setSessionCountdown(0);

View File

@@ -74,6 +74,7 @@ function Application({ setBreadcrumb }) {
), ),
link: '/applications', link: '/applications',
id: 0, id: 0,
isBackButton: true,
}, },
{ name: t`Details`, link: `/applications/${id}/details`, id: 1 }, { name: t`Details`, link: `/applications/${id}/details`, id: 1 },
{ name: t`Tokens`, link: `/applications/${id}/tokens`, id: 2 }, { name: t`Tokens`, link: `/applications/${id}/tokens`, id: 2 },

View File

@@ -11,6 +11,7 @@ import {
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import { Detail, DetailList } from 'components/DetailList'; import { Detail, DetailList } from 'components/DetailList';
import PersistentFilters from 'components/PersistentFilters';
import ApplicationsList from './ApplicationsList'; import ApplicationsList from './ApplicationsList';
import ApplicationAdd from './ApplicationAdd'; import ApplicationAdd from './ApplicationAdd';
import Application from './Application'; import Application from './Application';
@@ -56,7 +57,9 @@ function Applications() {
<Application setBreadcrumb={buildBreadcrumbConfig} /> <Application setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/applications"> <Route path="/applications">
<ApplicationsList /> <PersistentFilters pageKey="applications">
<ApplicationsList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
{applicationModalSource && ( {applicationModalSource && (

View File

@@ -67,6 +67,7 @@ function Credential({ setBreadcrumb }) {
), ),
link: `/credentials`, link: `/credentials`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `/credentials/${id}/details`, id: 0 }, { name: t`Details`, link: `/credentials/${id}/details`, id: 0 },
{ {

View File

@@ -4,6 +4,7 @@ import { Route, Switch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Config } from 'contexts/Config'; import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import Credential from './Credential'; import Credential from './Credential';
import CredentialAdd from './CredentialAdd'; import CredentialAdd from './CredentialAdd';
import { CredentialList } from './CredentialList'; import { CredentialList } from './CredentialList';
@@ -44,7 +45,9 @@ function Credentials() {
<Credential setBreadcrumb={buildBreadcrumbConfig} /> <Credential setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/credentials"> <Route path="/credentials">
<CredentialList /> <PersistentFilters pageKey="credentials">
<CredentialList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -57,6 +57,7 @@ function CredentialType({ setBreadcrumb }) {
), ),
link: '/credential_types', link: '/credential_types',
id: 99, id: 99,
isBackButton: true,
}, },
{ {
name: t`Details`, name: t`Details`,

View File

@@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import PersistentFilters from 'components/PersistentFilters';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import CredentialTypeAdd from './CredentialTypeAdd'; import CredentialTypeAdd from './CredentialTypeAdd';
import CredentialTypeList from './CredentialTypeList'; import CredentialTypeList from './CredentialTypeList';
@@ -40,7 +40,9 @@ function CredentialTypes() {
<CredentialType setBreadcrumb={buildBreadcrumbConfig} /> <CredentialType setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/credential_types"> <Route path="/credential_types">
<CredentialTypeList /> <PersistentFilters pageKey="credentialTypes">
<CredentialTypeList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -59,6 +59,7 @@ function ExecutionEnvironment({ setBreadcrumb }) {
), ),
link: '/execution_environments', link: '/execution_environments',
id: 99, id: 99,
isBackButton: true,
}, },
{ {
name: t`Details`, name: t`Details`,

View File

@@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import PersistentFilters from 'components/PersistentFilters';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import ExecutionEnvironment from './ExecutionEnvironment'; import ExecutionEnvironment from './ExecutionEnvironment';
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd'; import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
@@ -40,7 +40,9 @@ function ExecutionEnvironments() {
<ExecutionEnvironment setBreadcrumb={buildBreadcrumbConfig} /> <ExecutionEnvironment setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/execution_environments"> <Route path="/execution_environments">
<ExecutionEnvironmentList /> <PersistentFilters pageKey="executionEnvironments">
<ExecutionEnvironmentList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -52,6 +52,7 @@ function Host({ setBreadcrumb }) {
), ),
link: `/hosts`, link: `/hosts`,
id: 99, id: 99,
isBackButton: true,
}, },
{ {
name: t`Details`, name: t`Details`,

View File

@@ -1,11 +1,10 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Config } from 'contexts/Config'; import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import HostList from './HostList'; import HostList from './HostList';
import HostAdd from './HostAdd'; import HostAdd from './HostAdd';
import Host from './Host'; import Host from './Host';
@@ -47,7 +46,9 @@ function Hosts() {
</Config> </Config>
</Route> </Route>
<Route path="/hosts"> <Route path="/hosts">
<HostList /> <PersistentFilters pageKey="hosts">
<HostList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -63,6 +63,7 @@ function InstanceGroup({ setBreadcrumb }) {
), ),
link: '/instance_groups', link: '/instance_groups',
id: 99, id: 99,
isBackButton: true,
}, },
{ {
name: t`Details`, name: t`Details`,

View File

@@ -9,6 +9,7 @@ import useRequest from 'hooks/useRequest';
import { SettingsAPI } from 'api'; import { SettingsAPI } from 'api';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import ContentLoading from 'components/ContentLoading'; import ContentLoading from 'components/ContentLoading';
import PersistentFilters from 'components/PersistentFilters';
import InstanceGroupAdd from './InstanceGroupAdd'; import InstanceGroupAdd from './InstanceGroupAdd';
import InstanceGroupList from './InstanceGroupList'; import InstanceGroupList from './InstanceGroupList';
import InstanceGroup from './InstanceGroup'; import InstanceGroup from './InstanceGroup';
@@ -103,11 +104,13 @@ function InstanceGroups() {
<InstanceGroup setBreadcrumb={buildBreadcrumbConfig} /> <InstanceGroup setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/instance_groups"> <Route path="/instance_groups">
<InstanceGroupList <PersistentFilters pageKey="instanceGroups">
isKubernetes={isKubernetes} <InstanceGroupList
isSettingsRequestLoading={isSettingsRequestLoading} isKubernetes={isKubernetes}
settingsRequestError={settingsRequestError} isSettingsRequestLoading={isSettingsRequestLoading}
/> settingsRequestError={settingsRequestError}
/>
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
)} )}

View File

@@ -20,6 +20,7 @@ function Instance({ setBreadcrumb }) {
), ),
link: `/instances`, link: `/instances`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `${match.url}/details`, id: 0 }, { name: t`Details`, link: `${match.url}/details`, id: 0 },
]; ];

View File

@@ -3,6 +3,7 @@ import React, { useCallback, useState } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import { InstanceList } from './InstanceList'; import { InstanceList } from './InstanceList';
import Instance from './Instance'; import Instance from './Instance';
@@ -30,7 +31,9 @@ function Instances() {
<Instance setBreadcrumb={buildBreadcrumbConfig} /> <Instance setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/instances"> <Route path="/instances">
<InstanceList /> <PersistentFilters pageKey="instances">
<InstanceList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -5,6 +5,7 @@ import { Route, Switch } from 'react-router-dom';
import { Config } from 'contexts/Config'; import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import { InventoryList } from './InventoryList'; import { InventoryList } from './InventoryList';
import Inventory from './Inventory'; import Inventory from './Inventory';
import SmartInventory from './SmartInventory'; import SmartInventory from './SmartInventory';
@@ -119,7 +120,9 @@ function Inventories() {
<SmartInventory setBreadcrumb={setBreadcrumbConfig} /> <SmartInventory setBreadcrumb={setBreadcrumbConfig} />
</Route> </Route>
<Route path="/inventories"> <Route path="/inventories">
<InventoryList /> <PersistentFilters pageKey="inventories">
<InventoryList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -59,6 +59,7 @@ function Inventory({ setBreadcrumb }) {
), ),
link: `/inventories`, link: `/inventories`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `${match.url}/details`, id: 0 }, { name: t`Details`, link: `${match.url}/details`, id: 0 },
{ name: t`Access`, link: `${match.url}/access`, id: 1 }, { name: t`Access`, link: `${match.url}/access`, id: 1 },

View File

@@ -59,12 +59,14 @@ describe('<InventoryGroup />', () => {
}); });
test('expect all tabs to exist, including Back to Groups', async () => { test('expect all tabs to exist, including Back to Groups', async () => {
expect( const routedTabs = wrapper.find('RoutedTabs');
wrapper.find('button[link="/inventories/inventory/1/groups"]').length expect(routedTabs).toHaveLength(1);
).toBe(1);
expect(wrapper.find('button[aria-label="Details"]').length).toBe(1); const tabs = routedTabs.prop('tabsArray');
expect(wrapper.find('button[aria-label="Related Groups"]').length).toBe(1); expect(tabs[0].link).toEqual('/inventories/inventory/1/groups');
expect(wrapper.find('button[aria-label="Hosts"]').length).toBe(1); expect(tabs[1].name).toEqual('Details');
expect(tabs[2].name).toEqual('Related Groups');
expect(tabs[3].name).toEqual('Hosts');
}); });
test('should show content error when user attempts to navigate to erroneous route', async () => { test('should show content error when user attempts to navigate to erroneous route', async () => {

View File

@@ -111,6 +111,7 @@ function Job({ setBreadcrumb }) {
</> </>
), ),
link: `/jobs`, link: `/jobs`,
isBackButton: true,
id: 99, id: 99,
}, },
{ name: t`Details`, link: `${match.url}/details`, id: 0 }, { name: t`Details`, link: `${match.url}/details`, id: 0 },

View File

@@ -5,6 +5,7 @@ import { t } from '@lingui/macro';
import { PageSection } from '@patternfly/react-core'; import { PageSection } from '@patternfly/react-core';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import JobList from 'components/JobList'; import JobList from 'components/JobList';
import PersistentFilters from 'components/PersistentFilters';
import Job from './Job'; import Job from './Job';
import JobTypeRedirect from './JobTypeRedirect'; import JobTypeRedirect from './JobTypeRedirect';
import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
@@ -41,7 +42,9 @@ function Jobs() {
<Switch> <Switch>
<Route exact path={match.path}> <Route exact path={match.path}>
<PageSection> <PageSection>
<JobList showTypeColumn /> <PersistentFilters pageKey="jobs">
<JobList showTypeColumn />
</PersistentFilters>
</PageSection> </PageSection>
</Route> </Route>
<Route path={`${match.path}/:id/details`}> <Route path={`${match.path}/:id/details`}>

View File

@@ -98,6 +98,7 @@ function ManagementJob({ setBreadcrumb }) {
{t`Back to management jobs`} {t`Back to management jobs`}
</> </>
), ),
isBackButton: true,
}, },
]; ];

View File

@@ -1,9 +1,8 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import ManagementJob from './ManagementJob'; import ManagementJob from './ManagementJob';
import ManagementJobList from './ManagementJobList'; import ManagementJobList from './ManagementJobList';
@@ -37,7 +36,9 @@ function ManagementJobs() {
<ManagementJob setBreadcrumb={buildBreadcrumbConfig} /> <ManagementJob setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path={basePath}> <Route path={basePath}>
<ManagementJobList setBreadcrumb={buildBreadcrumbConfig} /> <PersistentFilters pageKey="managementJobs">
<ManagementJobList setBreadcrumb={buildBreadcrumbConfig} />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -78,6 +78,7 @@ function NotificationTemplate({ setBreadcrumb }) {
), ),
link: `/notification_templates`, link: `/notification_templates`,
id: 99, id: 99,
isBackButton: true,
}, },
{ {
name: t`Details`, name: t`Details`,

View File

@@ -3,6 +3,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import NotificationTemplateList from './NotificationTemplateList'; import NotificationTemplateList from './NotificationTemplateList';
import NotificationTemplateAdd from './NotificationTemplateAdd'; import NotificationTemplateAdd from './NotificationTemplateAdd';
import NotificationTemplate from './NotificationTemplate'; import NotificationTemplate from './NotificationTemplate';
@@ -39,7 +40,9 @@ function NotificationTemplates() {
<NotificationTemplate setBreadcrumb={updateBreadcrumbConfig} /> <NotificationTemplate setBreadcrumb={updateBreadcrumbConfig} />
</Route> </Route>
<Route path={`${match.url}`}> <Route path={`${match.url}`}>
<NotificationTemplateList /> <PersistentFilters pageKey="notificationTemplates">
<NotificationTemplateList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -118,6 +118,7 @@ function Organization({ setBreadcrumb, me }) {
), ),
link: `/organizations`, link: `/organizations`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `${match.url}/details`, id: 0 }, { name: t`Details`, link: `${match.url}/details`, id: 0 },
{ name: t`Access`, link: `${match.url}/access`, id: 1 }, { name: t`Access`, link: `${match.url}/access`, id: 1 },

View File

@@ -5,7 +5,7 @@ import { t } from '@lingui/macro';
import { Config } from 'contexts/Config'; import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import OrganizationsList from './OrganizationList/OrganizationList'; import OrganizationsList from './OrganizationList/OrganizationList';
import OrganizationAdd from './OrganizationAdd/OrganizationAdd'; import OrganizationAdd from './OrganizationAdd/OrganizationAdd';
import Organization from './Organization'; import Organization from './Organization';
@@ -54,7 +54,9 @@ function Organizations() {
</Config> </Config>
</Route> </Route>
<Route path={`${match.path}`}> <Route path={`${match.path}`}>
<OrganizationsList /> <PersistentFilters pageKey="organizations">
<OrganizationsList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -99,6 +99,7 @@ function Project({ setBreadcrumb }) {
), ),
link: `/projects`, link: `/projects`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `/projects/${id}/details` }, { name: t`Details`, link: `/projects/${id}/details` },
{ name: t`Access`, link: `/projects/${id}/access` }, { name: t`Access`, link: `/projects/${id}/access` },

View File

@@ -1,10 +1,8 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Route, withRouter, Switch } from 'react-router-dom'; import { Route, withRouter, Switch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import ProjectsList from './ProjectList/ProjectList'; import ProjectsList from './ProjectList/ProjectList';
import ProjectAdd from './ProjectAdd/ProjectAdd'; import ProjectAdd from './ProjectAdd/ProjectAdd';
import Project from './Project'; import Project from './Project';
@@ -49,7 +47,9 @@ function Projects() {
<Project setBreadcrumb={buildBreadcrumbConfig} /> <Project setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/projects"> <Route path="/projects">
<ProjectsList /> <PersistentFilters pageKey="projects">
<ProjectsList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -52,6 +52,7 @@ function Team({ setBreadcrumb }) {
), ),
link: `/teams`, link: `/teams`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `/teams/${id}/details`, id: 0 }, { name: t`Details`, link: `/teams/${id}/details`, id: 0 },
{ name: t`Access`, link: `/teams/${id}/access`, id: 1 }, { name: t`Access`, link: `/teams/${id}/access`, id: 1 },

View File

@@ -5,6 +5,7 @@ import { t } from '@lingui/macro';
import { Config } from 'contexts/Config'; import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import TeamList from './TeamList'; import TeamList from './TeamList';
import TeamAdd from './TeamAdd'; import TeamAdd from './TeamAdd';
import Team from './Team'; import Team from './Team';
@@ -43,9 +44,11 @@ function Teams() {
<Team setBreadcrumb={buildBreadcrumbConfig} /> <Team setBreadcrumb={buildBreadcrumbConfig} />
</Route> </Route>
<Route path="/teams"> <Route path="/teams">
<Config> <PersistentFilters pageKey="teams">
{({ me }) => <TeamList path="/teams" me={me || {}} />} <Config>
</Config> {({ me }) => <TeamList path="/teams" me={me || {}} />}
</Config>
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -129,6 +129,7 @@ function Template({ setBreadcrumb }) {
</> </>
), ),
link: `/templates`, link: `/templates`,
isBackButton: true,
id: 99, id: 99,
}, },
{ name: t`Details`, link: `${match.url}/details` }, { name: t`Details`, link: `${match.url}/details` },

View File

@@ -6,6 +6,7 @@ import { PageSection } from '@patternfly/react-core';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import TemplateList from 'components/TemplateList'; import TemplateList from 'components/TemplateList';
import PersistentFilters from 'components/PersistentFilters';
import Template from './Template'; import Template from './Template';
import WorkflowJobTemplate from './WorkflowJobTemplate'; import WorkflowJobTemplate from './WorkflowJobTemplate';
import JobTemplateAdd from './JobTemplateAdd'; import JobTemplateAdd from './JobTemplateAdd';
@@ -78,7 +79,9 @@ function Templates() {
</Route> </Route>
<Route path="/templates"> <Route path="/templates">
<PageSection> <PageSection>
<TemplateList /> <PersistentFilters pageKey="templates">
<TemplateList />
</PersistentFilters>
</PageSection> </PageSection>
</Route> </Route>
</Switch> </Switch>

View File

@@ -111,6 +111,7 @@ function WorkflowJobTemplate({ setBreadcrumb }) {
</> </>
), ),
link: `/templates`, link: `/templates`,
isBackButton: true,
id: 99, id: 99,
}, },
{ name: t`Details`, link: `${match.url}/details` }, { name: t`Details`, link: `${match.url}/details` },

View File

@@ -59,6 +59,7 @@ function User({ setBreadcrumb, me }) {
), ),
link: `/users`, link: `/users`,
id: 99, id: 99,
isBackButton: true,
}, },
{ name: t`Details`, link: `${match.url}/details`, id: 0 }, { name: t`Details`, link: `${match.url}/details`, id: 0 },
{ {

View File

@@ -4,8 +4,8 @@ import { Route, useRouteMatch, Switch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import { Config } from 'contexts/Config'; import { Config } from 'contexts/Config';
import UsersList from './UserList/UserList'; import UsersList from './UserList/UserList';
import UserAdd from './UserAdd/UserAdd'; import UserAdd from './UserAdd/UserAdd';
import User from './User'; import User from './User';
@@ -51,7 +51,9 @@ function Users() {
</Config> </Config>
</Route> </Route>
<Route path={`${match.path}`}> <Route path={`${match.path}`}>
<UsersList /> <PersistentFilters pageKey="users">
<UsersList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>

View File

@@ -70,6 +70,7 @@ function WorkflowApproval({ setBreadcrumb }) {
</> </>
), ),
link: `/workflow_approvals`, link: `/workflow_approvals`,
isBackButton: true,
id: 99, id: 99,
}, },
{ {

View File

@@ -3,6 +3,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader'; import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import PersistentFilters from 'components/PersistentFilters';
import WorkflowApprovalList from './WorkflowApprovalList'; import WorkflowApprovalList from './WorkflowApprovalList';
import WorkflowApproval from './WorkflowApproval'; import WorkflowApproval from './WorkflowApproval';
@@ -35,7 +36,9 @@ function WorkflowApprovals() {
<WorkflowApproval setBreadcrumb={updateBreadcrumbConfig} /> <WorkflowApproval setBreadcrumb={updateBreadcrumbConfig} />
</Route> </Route>
<Route path={`${match.url}`}> <Route path={`${match.url}`}>
<WorkflowApprovalList /> <PersistentFilters pageKey="workflowApprovals">
<WorkflowApprovalList />
</PersistentFilters>
</Route> </Route>
</Switch> </Switch>
</> </>