Adds support for pendo initialization across the app

This commit is contained in:
mabashian 2021-05-03 17:37:24 -04:00
parent 550a66553e
commit 3a56d2447c
10 changed files with 124 additions and 46 deletions

View File

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

View File

@ -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('<AppContainer />', () => {
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('<AppContainer />', () => {
{routeConfig.map(({ groupId }) => (
<div key={groupId} id={groupId} />
))}
</AppContainer>
</AppContainer>,
{
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('<AppContainer />', () => {
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(<AppContainer />, {
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 () => {

View File

@ -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,8 +22,6 @@ export const useConfig = () => {
};
export const ConfigProvider = ({ children }) => {
const { pathname } = useLocation();
const {
error: configError,
isLoading,
@ -48,10 +46,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) {

View File

@ -18,7 +18,6 @@ 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';
@ -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,18 @@ function SubscriptionEdit() {
});
}
}
const [
{ data },
{
data: {
results: [me],
},
},
] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
const newConfig = { ...data, me };
setConfig(newConfig);
return true;
}, []) // eslint-disable-line react-hooks/exhaustive-deps
);

View File

@ -14,7 +14,6 @@ import {
} from '../../../../api';
import SubscriptionEdit from './SubscriptionEdit';
jest.mock('./bootstrapPendo');
jest.mock('../../../../api');
const mockConfig = {

View File

@ -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 () => {

View File

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

View File

@ -111,6 +111,21 @@ describe('<UIEdit />', () => {
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 () => {

View File

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