diff --git a/awx/ui/.eslintrc.json b/awx/ui/.eslintrc.json
index ca8352549e..7cf4965cbd 100644
--- a/awx/ui/.eslintrc.json
+++ b/awx/ui/.eslintrc.json
@@ -11,7 +11,7 @@
},
"babelOptions": {
"presets": ["@babel/preset-react"]
- }
+ }
},
"plugins": ["react-hooks", "jsx-a11y", "i18next", "@babel"],
"extends": [
@@ -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",
@@ -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", {}],
diff --git a/awx/ui/src/components/PersistentFilters/PersistentFilters.js b/awx/ui/src/components/PersistentFilters/PersistentFilters.js
new file mode 100644
index 0000000000..2fb754ac5a
--- /dev/null
+++ b/awx/ui/src/components/PersistentFilters/PersistentFilters.js
@@ -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;
+}
diff --git a/awx/ui/src/components/PersistentFilters/PersistentFilters.test.js b/awx/ui/src/components/PersistentFilters/PersistentFilters.test.js
new file mode 100644
index 0000000000..bb13f56c0f
--- /dev/null
+++ b/awx/ui/src/components/PersistentFilters/PersistentFilters.test.js
@@ -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(
+
+ test
+
+ );
+
+ 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(
+
+ test
+
+ );
+
+ 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(
+
+ test
+
+ );
+
+ 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(
+
+ test
+
+ );
+
+ expect(history.location.search).toEqual('');
+ });
+
+ test('should update stored filters when qs changes', async () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/templates'],
+ });
+ render(
+
+ test
+
+ );
+
+ history.push('/templates?page=3');
+ await waitFor(() => true);
+
+ expect(JSON.parse(sessionStorage.getItem(KEY))).toEqual({
+ pageKey: 'templates',
+ qs: '?page=3',
+ });
+ });
+});
diff --git a/awx/ui/src/components/PersistentFilters/index.js b/awx/ui/src/components/PersistentFilters/index.js
new file mode 100644
index 0000000000..ee9b84f9db
--- /dev/null
+++ b/awx/ui/src/components/PersistentFilters/index.js
@@ -0,0 +1 @@
+export { default } from './PersistentFilters';
diff --git a/awx/ui/src/components/RoutedTabs/RoutedTabs.js b/awx/ui/src/components/RoutedTabs/RoutedTabs.js
index 9cc6ab920d..f68d6d733b 100644
--- a/awx/ui/src/components/RoutedTabs/RoutedTabs.js
+++ b/awx/ui/src/components/RoutedTabs/RoutedTabs.js
@@ -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);
}
};
@@ -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={{tab.name}}
aria-controls=""
ouiaId={`${tab.name}-tab`}
diff --git a/awx/ui/src/components/RoutedTabs/RoutedTabs.test.js b/awx/ui/src/components/RoutedTabs/RoutedTabs.test.js
index 4e31560603..475723b000 100644
--- a/awx/ui/src/components/RoutedTabs/RoutedTabs.test.js
+++ b/awx/ui/src/components/RoutedTabs/RoutedTabs.test.js
@@ -37,7 +37,12 @@ describe('', () => {
});
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');
diff --git a/awx/ui/src/components/Schedule/Schedule.test.js b/awx/ui/src/components/Schedule/Schedule.test.js
index 21c1636a8d..e9432c8725 100644
--- a/awx/ui/src/components/Schedule/Schedule.test.js
+++ b/awx/ui/src/components/Schedule/Schedule.test.js
@@ -119,9 +119,10 @@ describe('', () => {
});
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');
});
});
diff --git a/awx/ui/src/constants.js b/awx/ui/src/constants.js
index 092bdcd5b0..303061e3ea 100644
--- a/awx/ui/src/constants.js
+++ b/awx/ui/src/constants.js
@@ -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';
diff --git a/awx/ui/src/contexts/Session.js b/awx/ui/src/contexts/Session.js
index df01ae6f07..3a33fb3f62 100644
--- a/awx/ui/src/contexts/Session.js
+++ b/awx/ui/src/contexts/Session.js
@@ -102,6 +102,7 @@ function SessionProvider({ children }) {
if (!isSessionExpired.current) {
setAuthRedirectTo('/logout');
}
+ sessionStorage.clear();
await RootAPI.logout();
setSessionTimeout(0);
setSessionCountdown(0);
diff --git a/awx/ui/src/screens/Application/Application/Application.js b/awx/ui/src/screens/Application/Application/Application.js
index bd7da2d72c..e6d3a04ed0 100644
--- a/awx/ui/src/screens/Application/Application/Application.js
+++ b/awx/ui/src/screens/Application/Application/Application.js
@@ -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 },
diff --git a/awx/ui/src/screens/Application/Applications.js b/awx/ui/src/screens/Application/Applications.js
index 5ef267f9e7..37b60fa7ff 100644
--- a/awx/ui/src/screens/Application/Applications.js
+++ b/awx/ui/src/screens/Application/Applications.js
@@ -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';
@@ -56,7 +57,9 @@ function Applications() {
-
+
+
+
{applicationModalSource && (
diff --git a/awx/ui/src/screens/Credential/Credential.js b/awx/ui/src/screens/Credential/Credential.js
index ba580fd76e..45c507e23e 100644
--- a/awx/ui/src/screens/Credential/Credential.js
+++ b/awx/ui/src/screens/Credential/Credential.js
@@ -67,6 +67,7 @@ function Credential({ setBreadcrumb }) {
),
link: `/credentials`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `/credentials/${id}/details`, id: 0 },
{
diff --git a/awx/ui/src/screens/Credential/Credentials.js b/awx/ui/src/screens/Credential/Credentials.js
index cb213b5534..f7f38e8179 100644
--- a/awx/ui/src/screens/Credential/Credentials.js
+++ b/awx/ui/src/screens/Credential/Credentials.js
@@ -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';
@@ -44,7 +45,9 @@ function Credentials() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/CredentialType/CredentialType.js b/awx/ui/src/screens/CredentialType/CredentialType.js
index 07942674f9..4526629097 100644
--- a/awx/ui/src/screens/CredentialType/CredentialType.js
+++ b/awx/ui/src/screens/CredentialType/CredentialType.js
@@ -57,6 +57,7 @@ function CredentialType({ setBreadcrumb }) {
),
link: '/credential_types',
id: 99,
+ isBackButton: true,
},
{
name: t`Details`,
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypes.js b/awx/ui/src/screens/CredentialType/CredentialTypes.js
index f99ff1ae35..6178091e83 100644
--- a/awx/ui/src/screens/CredentialType/CredentialTypes.js
+++ b/awx/ui/src/screens/CredentialType/CredentialTypes.js
@@ -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';
@@ -40,7 +40,9 @@ function CredentialTypes() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js
index 846bd31433..8896025bb6 100644
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js
+++ b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js
@@ -59,6 +59,7 @@ function ExecutionEnvironment({ setBreadcrumb }) {
),
link: '/execution_environments',
id: 99,
+ isBackButton: true,
},
{
name: t`Details`,
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js
index 952eef00fd..e2a9a26cf6 100644
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js
+++ b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js
@@ -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';
@@ -40,7 +40,9 @@ function ExecutionEnvironments() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/Host/Host.js b/awx/ui/src/screens/Host/Host.js
index 728eac8c10..d62e34c693 100644
--- a/awx/ui/src/screens/Host/Host.js
+++ b/awx/ui/src/screens/Host/Host.js
@@ -52,6 +52,7 @@ function Host({ setBreadcrumb }) {
),
link: `/hosts`,
id: 99,
+ isBackButton: true,
},
{
name: t`Details`,
diff --git a/awx/ui/src/screens/Host/Hosts.js b/awx/ui/src/screens/Host/Hosts.js
index d104b3ec68..522acd124e 100644
--- a/awx/ui/src/screens/Host/Hosts.js
+++ b/awx/ui/src/screens/Host/Hosts.js
@@ -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';
@@ -47,7 +46,9 @@ function Hosts() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroup.js b/awx/ui/src/screens/InstanceGroup/InstanceGroup.js
index 510e3838f5..b4d0d404c5 100644
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroup.js
+++ b/awx/ui/src/screens/InstanceGroup/InstanceGroup.js
@@ -63,6 +63,7 @@ function InstanceGroup({ setBreadcrumb }) {
),
link: '/instance_groups',
id: 99,
+ isBackButton: true,
},
{
name: t`Details`,
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroups.js b/awx/ui/src/screens/InstanceGroup/InstanceGroups.js
index d1ff418e12..f5a4783c33 100644
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroups.js
+++ b/awx/ui/src/screens/InstanceGroup/InstanceGroups.js
@@ -9,6 +9,7 @@ import useRequest from 'hooks/useRequest';
import { SettingsAPI } from 'api';
import ScreenHeader from 'components/ScreenHeader';
import ContentLoading from 'components/ContentLoading';
+import PersistentFilters from 'components/PersistentFilters';
import InstanceGroupAdd from './InstanceGroupAdd';
import InstanceGroupList from './InstanceGroupList';
import InstanceGroup from './InstanceGroup';
@@ -103,11 +104,13 @@ function InstanceGroups() {
-
+
+
+
)}
diff --git a/awx/ui/src/screens/Instances/Instance.js b/awx/ui/src/screens/Instances/Instance.js
index 585315d946..8efe4b55f6 100644
--- a/awx/ui/src/screens/Instances/Instance.js
+++ b/awx/ui/src/screens/Instances/Instance.js
@@ -20,6 +20,7 @@ function Instance({ setBreadcrumb }) {
),
link: `/instances`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `${match.url}/details`, id: 0 },
];
diff --git a/awx/ui/src/screens/Instances/Instances.js b/awx/ui/src/screens/Instances/Instances.js
index 271c9da420..a230fb9a67 100644
--- a/awx/ui/src/screens/Instances/Instances.js
+++ b/awx/ui/src/screens/Instances/Instances.js
@@ -3,6 +3,7 @@ import React, { useCallback, useState } from 'react';
import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom';
import ScreenHeader from 'components/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import { InstanceList } from './InstanceList';
import Instance from './Instance';
@@ -30,7 +31,9 @@ function Instances() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/Inventory/Inventories.js b/awx/ui/src/screens/Inventory/Inventories.js
index cb4e51b712..49bf4d7710 100644
--- a/awx/ui/src/screens/Inventory/Inventories.js
+++ b/awx/ui/src/screens/Inventory/Inventories.js
@@ -5,6 +5,7 @@ import { Route, Switch } from 'react-router-dom';
import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import { InventoryList } from './InventoryList';
import Inventory from './Inventory';
import SmartInventory from './SmartInventory';
@@ -119,7 +120,9 @@ function Inventories() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/Inventory/Inventory.js b/awx/ui/src/screens/Inventory/Inventory.js
index c0bf58c39b..d26c0fc5ce 100644
--- a/awx/ui/src/screens/Inventory/Inventory.js
+++ b/awx/ui/src/screens/Inventory/Inventory.js
@@ -59,6 +59,7 @@ function Inventory({ setBreadcrumb }) {
),
link: `/inventories`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `${match.url}/details`, id: 0 },
{ name: t`Access`, link: `${match.url}/access`, id: 1 },
diff --git a/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js b/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js
index 6a671420db..ee468bf7d4 100644
--- a/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js
+++ b/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js
@@ -59,12 +59,14 @@ describe('', () => {
});
test('expect all tabs to exist, including Back to Groups', async () => {
- expect(
- wrapper.find('button[link="/inventories/inventory/1/groups"]').length
- ).toBe(1);
- expect(wrapper.find('button[aria-label="Details"]').length).toBe(1);
- expect(wrapper.find('button[aria-label="Related Groups"]').length).toBe(1);
- expect(wrapper.find('button[aria-label="Hosts"]').length).toBe(1);
+ const routedTabs = wrapper.find('RoutedTabs');
+ expect(routedTabs).toHaveLength(1);
+
+ const tabs = routedTabs.prop('tabsArray');
+ expect(tabs[0].link).toEqual('/inventories/inventory/1/groups');
+ 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 () => {
diff --git a/awx/ui/src/screens/Job/Job.js b/awx/ui/src/screens/Job/Job.js
index ef0986bd1d..5ac0a3c347 100644
--- a/awx/ui/src/screens/Job/Job.js
+++ b/awx/ui/src/screens/Job/Job.js
@@ -111,6 +111,7 @@ function Job({ setBreadcrumb }) {
>
),
link: `/jobs`,
+ isBackButton: true,
id: 99,
},
{ name: t`Details`, link: `${match.url}/details`, id: 0 },
diff --git a/awx/ui/src/screens/Job/Jobs.js b/awx/ui/src/screens/Job/Jobs.js
index 1b28bf2eeb..aed47b5cd1 100644
--- a/awx/ui/src/screens/Job/Jobs.js
+++ b/awx/ui/src/screens/Job/Jobs.js
@@ -5,6 +5,7 @@ import { t } from '@lingui/macro';
import { PageSection } from '@patternfly/react-core';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import JobList from 'components/JobList';
+import PersistentFilters from 'components/PersistentFilters';
import Job from './Job';
import JobTypeRedirect from './JobTypeRedirect';
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
@@ -41,7 +42,9 @@ function Jobs() {
-
+
+
+
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJob.js b/awx/ui/src/screens/ManagementJob/ManagementJob.js
index 3673786f03..2cb4909668 100644
--- a/awx/ui/src/screens/ManagementJob/ManagementJob.js
+++ b/awx/ui/src/screens/ManagementJob/ManagementJob.js
@@ -98,6 +98,7 @@ function ManagementJob({ setBreadcrumb }) {
{t`Back to management jobs`}
>
),
+ isBackButton: true,
},
];
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobs.js b/awx/ui/src/screens/ManagementJob/ManagementJobs.js
index d4dafa1a01..6d0d13c3fb 100644
--- a/awx/ui/src/screens/ManagementJob/ManagementJobs.js
+++ b/awx/ui/src/screens/ManagementJob/ManagementJobs.js
@@ -1,9 +1,8 @@
import React, { useState, useCallback } from 'react';
-
import { t } from '@lingui/macro';
import { Route, Switch } from 'react-router-dom';
-
import ScreenHeader from 'components/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import ManagementJob from './ManagementJob';
import ManagementJobList from './ManagementJobList';
@@ -37,7 +36,9 @@ function ManagementJobs() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js
index 1e2eab210c..90f20f9b85 100644
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js
+++ b/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js
@@ -78,6 +78,7 @@ function NotificationTemplate({ setBreadcrumb }) {
),
link: `/notification_templates`,
id: 99,
+ isBackButton: true,
},
{
name: t`Details`,
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js
index 99a605e09d..37961f4bc2 100644
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js
+++ b/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js
@@ -3,6 +3,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import NotificationTemplateList from './NotificationTemplateList';
import NotificationTemplateAdd from './NotificationTemplateAdd';
import NotificationTemplate from './NotificationTemplate';
@@ -39,7 +40,9 @@ function NotificationTemplates() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/Organization/Organization.js b/awx/ui/src/screens/Organization/Organization.js
index 255f1ec05e..f47c32b2f5 100644
--- a/awx/ui/src/screens/Organization/Organization.js
+++ b/awx/ui/src/screens/Organization/Organization.js
@@ -118,6 +118,7 @@ function Organization({ setBreadcrumb, me }) {
),
link: `/organizations`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `${match.url}/details`, id: 0 },
{ name: t`Access`, link: `${match.url}/access`, id: 1 },
diff --git a/awx/ui/src/screens/Organization/Organizations.js b/awx/ui/src/screens/Organization/Organizations.js
index 8296105724..ce30bfac1c 100644
--- a/awx/ui/src/screens/Organization/Organizations.js
+++ b/awx/ui/src/screens/Organization/Organizations.js
@@ -5,7 +5,7 @@ import { t } from '@lingui/macro';
import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-
+import PersistentFilters from 'components/PersistentFilters';
import OrganizationsList from './OrganizationList/OrganizationList';
import OrganizationAdd from './OrganizationAdd/OrganizationAdd';
import Organization from './Organization';
@@ -54,7 +54,9 @@ function Organizations() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/Project/Project.js b/awx/ui/src/screens/Project/Project.js
index a3156ca151..cc185a8f74 100644
--- a/awx/ui/src/screens/Project/Project.js
+++ b/awx/ui/src/screens/Project/Project.js
@@ -99,6 +99,7 @@ function Project({ setBreadcrumb }) {
),
link: `/projects`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `/projects/${id}/details` },
{ name: t`Access`, link: `/projects/${id}/access` },
diff --git a/awx/ui/src/screens/Project/Projects.js b/awx/ui/src/screens/Project/Projects.js
index 4c80e7ecaf..919da3d9f5 100644
--- a/awx/ui/src/screens/Project/Projects.js
+++ b/awx/ui/src/screens/Project/Projects.js
@@ -1,10 +1,8 @@
import React, { useState, useCallback } from 'react';
import { Route, withRouter, Switch } from 'react-router-dom';
-
import { t } from '@lingui/macro';
-
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-
+import PersistentFilters from 'components/PersistentFilters';
import ProjectsList from './ProjectList/ProjectList';
import ProjectAdd from './ProjectAdd/ProjectAdd';
import Project from './Project';
@@ -49,7 +47,9 @@ function Projects() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/Team/Team.js b/awx/ui/src/screens/Team/Team.js
index 45caf2b45e..157aec583b 100644
--- a/awx/ui/src/screens/Team/Team.js
+++ b/awx/ui/src/screens/Team/Team.js
@@ -52,6 +52,7 @@ function Team({ setBreadcrumb }) {
),
link: `/teams`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `/teams/${id}/details`, id: 0 },
{ name: t`Access`, link: `/teams/${id}/access`, id: 1 },
diff --git a/awx/ui/src/screens/Team/Teams.js b/awx/ui/src/screens/Team/Teams.js
index 2302836e7e..e50a363317 100644
--- a/awx/ui/src/screens/Team/Teams.js
+++ b/awx/ui/src/screens/Team/Teams.js
@@ -5,6 +5,7 @@ import { t } from '@lingui/macro';
import { Config } from 'contexts/Config';
import ScreenHeader from 'components/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import TeamList from './TeamList';
import TeamAdd from './TeamAdd';
import Team from './Team';
@@ -43,9 +44,11 @@ function Teams() {
-
- {({ me }) => }
-
+
+
+ {({ me }) => }
+
+
>
diff --git a/awx/ui/src/screens/Template/Template.js b/awx/ui/src/screens/Template/Template.js
index e084d1f806..2c7801ed6d 100644
--- a/awx/ui/src/screens/Template/Template.js
+++ b/awx/ui/src/screens/Template/Template.js
@@ -129,6 +129,7 @@ function Template({ setBreadcrumb }) {
>
),
link: `/templates`,
+ isBackButton: true,
id: 99,
},
{ name: t`Details`, link: `${match.url}/details` },
diff --git a/awx/ui/src/screens/Template/Templates.js b/awx/ui/src/screens/Template/Templates.js
index 6e8811aa93..749094c327 100644
--- a/awx/ui/src/screens/Template/Templates.js
+++ b/awx/ui/src/screens/Template/Templates.js
@@ -6,6 +6,7 @@ import { PageSection } from '@patternfly/react-core';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import TemplateList from 'components/TemplateList';
+import PersistentFilters from 'components/PersistentFilters';
import Template from './Template';
import WorkflowJobTemplate from './WorkflowJobTemplate';
import JobTemplateAdd from './JobTemplateAdd';
@@ -78,7 +79,9 @@ function Templates() {
-
+
+
+
diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplate.js b/awx/ui/src/screens/Template/WorkflowJobTemplate.js
index 061124d357..ef8225146f 100644
--- a/awx/ui/src/screens/Template/WorkflowJobTemplate.js
+++ b/awx/ui/src/screens/Template/WorkflowJobTemplate.js
@@ -111,6 +111,7 @@ function WorkflowJobTemplate({ setBreadcrumb }) {
>
),
link: `/templates`,
+ isBackButton: true,
id: 99,
},
{ name: t`Details`, link: `${match.url}/details` },
diff --git a/awx/ui/src/screens/User/User.js b/awx/ui/src/screens/User/User.js
index 88b3e21731..d1d5371b5a 100644
--- a/awx/ui/src/screens/User/User.js
+++ b/awx/ui/src/screens/User/User.js
@@ -59,6 +59,7 @@ function User({ setBreadcrumb, me }) {
),
link: `/users`,
id: 99,
+ isBackButton: true,
},
{ name: t`Details`, link: `${match.url}/details`, id: 0 },
{
diff --git a/awx/ui/src/screens/User/Users.js b/awx/ui/src/screens/User/Users.js
index e198d4e5cc..44ebd1a99a 100644
--- a/awx/ui/src/screens/User/Users.js
+++ b/awx/ui/src/screens/User/Users.js
@@ -4,8 +4,8 @@ import { Route, useRouteMatch, Switch } from 'react-router-dom';
import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import { Config } from 'contexts/Config';
-
import UsersList from './UserList/UserList';
import UserAdd from './UserAdd/UserAdd';
import User from './User';
@@ -51,7 +51,9 @@ function Users() {
-
+
+
+
>
diff --git a/awx/ui/src/screens/WorkflowApproval/WorkflowApproval.js b/awx/ui/src/screens/WorkflowApproval/WorkflowApproval.js
index 92bdf9d633..07de41eb43 100644
--- a/awx/ui/src/screens/WorkflowApproval/WorkflowApproval.js
+++ b/awx/ui/src/screens/WorkflowApproval/WorkflowApproval.js
@@ -70,6 +70,7 @@ function WorkflowApproval({ setBreadcrumb }) {
>
),
link: `/workflow_approvals`,
+ isBackButton: true,
id: 99,
},
{
diff --git a/awx/ui/src/screens/WorkflowApproval/WorkflowApprovals.js b/awx/ui/src/screens/WorkflowApproval/WorkflowApprovals.js
index 45c72c9a1f..7749d12c0f 100644
--- a/awx/ui/src/screens/WorkflowApproval/WorkflowApprovals.js
+++ b/awx/ui/src/screens/WorkflowApproval/WorkflowApprovals.js
@@ -3,6 +3,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
+import PersistentFilters from 'components/PersistentFilters';
import WorkflowApprovalList from './WorkflowApprovalList';
import WorkflowApproval from './WorkflowApproval';
@@ -35,7 +36,9 @@ function WorkflowApprovals() {
-
+
+
+
>