diff --git a/awx/ui_next/src/components/AppContainer/AppContainer.jsx b/awx/ui_next/src/components/AppContainer/AppContainer.jsx
index 18db51495b..e697d27de7 100644
--- a/awx/ui_next/src/components/AppContainer/AppContainer.jsx
+++ b/awx/ui_next/src/components/AppContainer/AppContainer.jsx
@@ -19,6 +19,7 @@ import { MeAPI, RootAPI } from '../../api';
import { useConfig, useAuthorizedPath } from '../../contexts/Config';
import { SESSION_TIMEOUT_KEY } from '../../constants';
import { isAuthenticated } from '../../util/auth';
+import issuePendoIdentity from '../../util/issuePendoIdentity';
import About from '../About';
import AlertModal from '../AlertModal';
import BrandLogo from './BrandLogo';
@@ -138,6 +139,13 @@ function AppContainer({ navRouteConfig = [], children }) {
}
}, [handleLogout, timeRemaining]);
+ useEffect(() => {
+ if ('analytics_status' in config) {
+ issuePendoIdentity(config);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [config.analytics_status]);
+
const brandName = config?.license_info?.product_name;
const alt = brandName ? t`${brandName} logo` : t`brand logo`;
diff --git a/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx b/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx
index 8ba1260dbe..fdbde02a75 100644
--- a/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx
+++ b/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx
@@ -4,19 +4,25 @@ import {
mountWithContexts,
waitForElement,
} from '../../../testUtils/enzymeHelpers';
-import { ConfigAPI, MeAPI, RootAPI } from '../../api';
+import { MeAPI, RootAPI } from '../../api';
import { useAuthorizedPath } from '../../contexts/Config';
import AppContainer from './AppContainer';
jest.mock('../../api');
+jest.mock('../../util/bootstrapPendo');
+
+global.pendo = {
+ initialize: jest.fn(),
+};
describe('', () => {
const version = '222';
beforeEach(() => {
- ConfigAPI.read.mockResolvedValue({
+ RootAPI.readAssetVariables.mockResolvedValue({
data: {
- version,
+ BRAND_NAME: 'AWX',
+ PENDO_API_KEY: '',
},
});
MeAPI.read.mockResolvedValue({ data: { results: [{}] } });
@@ -52,7 +58,22 @@ describe('', () => {
{routeConfig.map(({ groupId }) => (
))}
-
+ ,
+ {
+ context: {
+ config: {
+ analytics_status: 'detailed',
+ ansible_version: null,
+ custom_virtualenvs: [],
+ version: '9000',
+ me: { is_superuser: true },
+ toJSON: () => '/config/',
+ license_info: {
+ valid_key: true,
+ },
+ },
+ },
+ }
);
});
wrapper.update();
@@ -70,6 +91,31 @@ describe('', () => {
expect(wrapper.find('#group_one').length).toBe(1);
expect(wrapper.find('#group_two').length).toBe(1);
+
+ expect(global.pendo.initialize).toHaveBeenCalledTimes(1);
+ });
+
+ test('expected content is rendered', async () => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(, {
+ context: {
+ config: {
+ analytics_status: 'off',
+ ansible_version: null,
+ custom_virtualenvs: [],
+ version: '9000',
+ me: { is_superuser: true },
+ toJSON: () => '/config/',
+ license_info: {
+ valid_key: true,
+ },
+ },
+ },
+ });
+ });
+ wrapper.update();
+ expect(global.pendo.initialize).toHaveBeenCalledTimes(0);
});
test('opening the about modal renders prefetched config data', async () => {
diff --git a/awx/ui_next/src/contexts/Config.jsx b/awx/ui_next/src/contexts/Config.jsx
index d2e184f32f..f95e5017a5 100644
--- a/awx/ui_next/src/contexts/Config.jsx
+++ b/awx/ui_next/src/contexts/Config.jsx
@@ -1,5 +1,5 @@
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
+import { useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro';
@@ -22,15 +22,7 @@ export const useConfig = () => {
};
export const ConfigProvider = ({ children }) => {
- const { pathname } = useLocation();
-
- const {
- error: configError,
- isLoading,
- request,
- result: config,
- setValue: setConfig,
- } = useRequest(
+ const { error: configError, isLoading, request, result: config } = useRequest(
useCallback(async () => {
const [
{ data },
@@ -48,10 +40,8 @@ export const ConfigProvider = ({ children }) => {
const { error, dismissError } = useDismissableError(configError);
useEffect(() => {
- if (pathname !== '/login') {
- request();
- }
- }, [request, pathname]);
+ request();
+ }, [request]);
useEffect(() => {
if (error?.response?.status === 401) {
@@ -59,10 +49,10 @@ export const ConfigProvider = ({ children }) => {
}
}, [error]);
- const value = useMemo(() => ({ ...config, isLoading, setConfig }), [
+ const value = useMemo(() => ({ ...config, request, isLoading }), [
config,
+ request,
isLoading,
- setConfig,
]);
return (
diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.jsx b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.jsx
index 272e808d35..ef0c8680f9 100644
--- a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.jsx
+++ b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.jsx
@@ -12,13 +12,12 @@ import {
WizardContextConsumer,
WizardFooter,
} from '@patternfly/react-core';
-import { ConfigAPI, SettingsAPI, MeAPI, RootAPI } from '../../../../api';
+import { ConfigAPI, SettingsAPI, RootAPI } from '../../../../api';
import useRequest, { useDismissableError } from '../../../../util/useRequest';
import ContentLoading from '../../../../components/ContentLoading';
import ContentError from '../../../../components/ContentError';
import { FormSubmitError } from '../../../../components/FormField';
import { useConfig } from '../../../../contexts/Config';
-import issuePendoIdentity from './pendoUtils';
import SubscriptionStep from './SubscriptionStep';
import AnalyticsStep from './AnalyticsStep';
import EulaStep from './EulaStep';
@@ -92,7 +91,7 @@ const CustomFooter = ({ isSubmitLoading }) => {
function SubscriptionEdit() {
const history = useHistory();
- const { license_info, setConfig } = useConfig();
+ const { request: updateConfig, license_info } = useConfig();
const hasValidKey = Boolean(license_info?.valid_key);
const subscriptionMgmtRoute = useRouteMatch({
path: '/subscription_management',
@@ -102,20 +101,18 @@ function SubscriptionEdit() {
isLoading: isContentLoading,
error: contentError,
request: fetchContent,
- result: { brandName, pendoApiKey },
+ result: { brandName },
} = useRequest(
useCallback(async () => {
const {
- data: { BRAND_NAME, PENDO_API_KEY },
+ data: { BRAND_NAME },
} = await RootAPI.readAssetVariables();
return {
brandName: BRAND_NAME,
- pendoApiKey: PENDO_API_KEY,
};
}, []),
{
brandName: null,
- pendoApiKey: null,
}
);
@@ -145,23 +142,11 @@ function SubscriptionEdit() {
});
}
- const [
- { data },
- {
- data: {
- results: [me],
- },
- },
- ] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
- const newConfig = { ...data, me };
- setConfig(newConfig);
-
if (!hasValidKey) {
if (form.pendo) {
await SettingsAPI.updateCategory('ui', {
PENDO_TRACKING_STATE: 'detailed',
});
- await issuePendoIdentity(newConfig, pendoApiKey);
} else {
await SettingsAPI.updateCategory('ui', {
PENDO_TRACKING_STATE: 'off',
@@ -178,6 +163,9 @@ function SubscriptionEdit() {
});
}
}
+
+ await updateConfig();
+
return true;
}, []) // eslint-disable-line react-hooks/exhaustive-deps
);
diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.test.jsx b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.test.jsx
index ee3d3d3395..9f69eb9fd5 100644
--- a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.test.jsx
+++ b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.test.jsx
@@ -14,7 +14,6 @@ import {
} from '../../../../api';
import SubscriptionEdit from './SubscriptionEdit';
-jest.mock('./bootstrapPendo');
jest.mock('../../../../api');
const mockConfig = {
@@ -53,7 +52,7 @@ const emptyConfig = {
license_info: {
valid_key: false,
},
- setConfig: jest.fn(),
+ request: jest.fn(),
};
describe('', () => {
@@ -269,7 +268,7 @@ describe('', () => {
context: {
config: {
mockConfig,
- setConfig: jest.fn(),
+ request: jest.fn(),
},
me: {
is_superuser: true,
diff --git a/awx/ui_next/src/screens/Setting/UI/UIDetail/UIDetail.jsx b/awx/ui_next/src/screens/Setting/UI/UIDetail/UIDetail.jsx
index d397a66c2d..803a56f9f7 100644
--- a/awx/ui_next/src/screens/Setting/UI/UIDetail/UIDetail.jsx
+++ b/awx/ui_next/src/screens/Setting/UI/UIDetail/UIDetail.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useCallback } from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useHistory, useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
@@ -19,6 +19,12 @@ import { SettingDetail } from '../../shared';
function UIDetail() {
const { me } = useConfig();
const { GET: options } = useSettings();
+ const history = useHistory();
+ const { hardReload } = useLocation();
+
+ if (hardReload) {
+ history.go();
+ }
const { isLoading, error, request, result: ui } = useRequest(
useCallback(async () => {
diff --git a/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.jsx b/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.jsx
index 42a85f57cb..f1b6e64309 100644
--- a/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.jsx
+++ b/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.jsx
@@ -49,9 +49,18 @@ function UIEdit() {
useCallback(
async values => {
await SettingsAPI.updateAll(values);
- history.push('/settings/ui/details');
+ if (
+ values?.PENDO_TRACKING_STATE !== uiData?.PENDO_TRACKING_STATE?.value
+ ) {
+ history.push({
+ pathname: '/settings/ui/details',
+ hardReload: true,
+ });
+ } else {
+ history.push('/settings/ui/details');
+ }
},
- [history]
+ [history, uiData]
),
null
);
diff --git a/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.test.jsx b/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.test.jsx
index 753075936a..7a607f476b 100644
--- a/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.test.jsx
+++ b/awx/ui_next/src/screens/Setting/UI/UIEdit/UIEdit.test.jsx
@@ -111,6 +111,21 @@ describe('', () => {
wrapper.find('Form').invoke('onSubmit')();
});
expect(history.location.pathname).toEqual('/settings/ui/details');
+ expect(history.location.hardReload).toEqual(undefined);
+ });
+
+ test('should navigate to ui detail with reload param on successful submission where PENDO_TRACKING_STATE changes', async () => {
+ act(() => {
+ wrapper.find('select#PENDO_TRACKING_STATE').simulate('change', {
+ target: { value: 'off', name: 'CUSTOM_LOGIN_INFO' },
+ });
+ });
+ wrapper.update();
+ await act(async () => {
+ wrapper.find('Form').invoke('onSubmit')();
+ });
+ expect(history.location.pathname).toEqual('/settings/ui/details');
+ expect(history.location.hardReload).toEqual(true);
});
test('should navigate to ui detail when cancel is clicked', async () => {
diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/bootstrapPendo.js b/awx/ui_next/src/util/bootstrapPendo.js
similarity index 100%
rename from awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/bootstrapPendo.js
rename to awx/ui_next/src/util/bootstrapPendo.js
diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/pendoUtils.js b/awx/ui_next/src/util/issuePendoIdentity.js
similarity index 72%
rename from awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/pendoUtils.js
rename to awx/ui_next/src/util/issuePendoIdentity.js
index e03cb4c53c..380a796113 100644
--- a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/pendoUtils.js
+++ b/awx/ui_next/src/util/issuePendoIdentity.js
@@ -1,30 +1,26 @@
-import { UsersAPI } from '../../../../api';
+import { RootAPI, UsersAPI } from '../api';
import bootstrapPendo from './bootstrapPendo';
function buildPendoOptions(config, pendoApiKey) {
- const tower_version = config.version.split('-')[0];
+ const towerVersion = config.version.split('-')[0];
const trial = config.trial ? config.trial : false;
- const options = {
+
+ return {
apiKey: pendoApiKey,
visitor: {
- id: null,
+ id: 0,
role: null,
},
account: {
- id: null,
+ id: 'tower.ansible.com',
planLevel: config.license_type,
planPrice: config.instance_count,
creationDate: config.license_date,
trial,
- tower_version,
+ tower_version: towerVersion,
ansible_version: config.ansible_version,
},
};
-
- options.visitor.id = 0;
- options.account.id = 'tower.ansible.com';
-
- return options;
}
async function buildPendoOptionsRole(options, config) {
@@ -45,14 +41,20 @@ async function buildPendoOptionsRole(options, config) {
}
}
-async function issuePendoIdentity(config, pendoApiKey) {
+async function issuePendoIdentity(config) {
+ if (!('license_info' in config)) {
+ config.license_info = {};
+ }
config.license_info.analytics_status = config.analytics_status;
config.license_info.version = config.version;
config.license_info.ansible_version = config.ansible_version;
if (config.analytics_status !== 'off') {
- bootstrapPendo(pendoApiKey);
- const pendoOptions = buildPendoOptions(config, pendoApiKey);
+ const {
+ data: { PENDO_API_KEY },
+ } = await RootAPI.readAssetVariables();
+ bootstrapPendo(PENDO_API_KEY);
+ const pendoOptions = buildPendoOptions(config, PENDO_API_KEY);
const pendoOptionsWithRole = await buildPendoOptionsRole(
pendoOptions,
config