Skip to content

Commit

Permalink
Refactor config context to return an array of two items: state and se…
Browse files Browse the repository at this point in the history
…tter

Add subscription wizard and redirect logic

sub step stage

sub modal

analytics step

Add ouiaId props, Trans tags, and Insight Analytics dashboard img

Pass config to context as an object

Add "modifier" to list of rules to ignore
  • Loading branch information
marshmalien committed Apr 6, 2021
1 parent 62be410 commit 476cfd7
Show file tree
Hide file tree
Showing 45 changed files with 2,496 additions and 279 deletions.
4 changes: 3 additions & 1 deletion awx/ui_next/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@
"src",
"theme",
"gridColumns",
"rows"
"rows",
"href",
"modifier"
],
"ignore": ["Ansible", "Tower", "JSON", "YAML", "lg"],
"ignoreComponent": [
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 50 additions & 17 deletions awx/ui_next/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
Redirect,
} from 'react-router-dom';
import { I18n, I18nProvider } from '@lingui/react';
import { Card, PageSection } from '@patternfly/react-core';

import { ConfigProvider, useAuthorizedPath } from './contexts/Config';
import AppContainer from './components/AppContainer';
import Background from './components/Background';
import NotFound from './screens/NotFound';
Expand All @@ -20,6 +22,49 @@ import { isAuthenticated } from './util/auth';
import { getLanguageWithoutRegionCode } from './util/language';

import getRouteConfig from './routeConfig';
import SubscriptionEdit from './screens/Setting/Subscription/SubscriptionEdit';

const AuthorizedRoutes = ({ routeConfig }) => {
const isAuthorized = useAuthorizedPath();
const match = useRouteMatch();

if (!isAuthorized) {
return (
<Switch>
<ProtectedRoute
key="/subscription_management"
path="/subscription_management"
>
<PageSection>
<Card>
<SubscriptionEdit />
</Card>
</PageSection>
</ProtectedRoute>
<Route path="*">
<Redirect to="/subscription_management" />
</Route>
</Switch>
);
}

return (
<Switch>
{routeConfig
.flatMap(({ routes }) => routes)
.map(({ path, screen: Screen }) => (
<ProtectedRoute key={path} path={path}>
<Screen match={match} />
</ProtectedRoute>
))
.concat(
<ProtectedRoute key="not-found" path="*">
<NotFound />
</ProtectedRoute>
)}
</Switch>
);
};

const ProtectedRoute = ({ children, ...rest }) =>
isAuthenticated(document.cookie) ? (
Expand All @@ -36,7 +81,6 @@ function App() {
// preferred language, default to one that has strings.
language = 'en';
}
const match = useRouteMatch();
const { hash, search, pathname } = useLocation();

return (
Expand All @@ -55,22 +99,11 @@ function App() {
<Redirect to="/home" />
</Route>
<ProtectedRoute>
<AppContainer navRouteConfig={getRouteConfig(i18n)}>
<Switch>
{getRouteConfig(i18n)
.flatMap(({ routes }) => routes)
.map(({ path, screen: Screen }) => (
<ProtectedRoute key={path} path={path}>
<Screen match={match} />
</ProtectedRoute>
))
.concat(
<ProtectedRoute key="not-found" path="*">
<NotFound />
</ProtectedRoute>
)}
</Switch>
</AppContainer>
<ConfigProvider>
<AppContainer navRouteConfig={getRouteConfig(i18n)}>
<AuthorizedRoutes routeConfig={getRouteConfig(i18n)} />
</AppContainer>
</ConfigProvider>
</ProtectedRoute>
</Switch>
</Background>
Expand Down
11 changes: 11 additions & 0 deletions awx/ui_next/src/api/models/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ class Config extends Base {
this.baseUrl = '/api/v2/config/';
this.read = this.read.bind(this);
}

readSubscriptions(username, password) {
return this.http.post(`${this.baseUrl}subscriptions/`, {
subscriptions_username: username,
subscriptions_password: password,
});
}

attach(data) {
return this.http.post(`${this.baseUrl}attach/`, data);
}
}

export default Config;
4 changes: 4 additions & 0 deletions awx/ui_next/src/api/models/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class Settings extends Base {
return this.http.patch(`${this.baseUrl}all/`, data);
}

updateCategory(category, data) {
return this.http.patch(`${this.baseUrl}${category}/`, data);
}

readCategory(category) {
return this.http.get(`${this.baseUrl}${category}/`);
}
Expand Down
78 changes: 33 additions & 45 deletions awx/ui_next/src/components/AppContainer/AppContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { useHistory, useLocation, withRouter } from 'react-router-dom';
import { useHistory, withRouter } from 'react-router-dom';
import {
Button,
Nav,
NavList,
Page,
PageHeader as PFPageHeader,
PageHeaderTools,
PageHeaderToolsGroup,
PageHeaderToolsItem,
PageSidebar,
} from '@patternfly/react-core';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import styled from 'styled-components';

import { ConfigAPI, MeAPI, RootAPI } from '../../api';
import { ConfigProvider } from '../../contexts/Config';
import { MeAPI, RootAPI } from '../../api';
import { useConfig, useAuthorizedPath } from '../../contexts/Config';
import { SESSION_TIMEOUT_KEY } from '../../constants';
import { isAuthenticated } from '../../util/auth';
import About from '../About';
import AlertModal from '../AlertModal';
import ErrorDetail from '../ErrorDetail';
import BrandLogo from './BrandLogo';
import NavExpandableGroup from './NavExpandableGroup';
import PageHeaderToolbar from './PageHeaderToolbar';
Expand Down Expand Up @@ -85,11 +87,11 @@ function useStorage(key) {

function AppContainer({ i18n, navRouteConfig = [], children }) {
const history = useHistory();
const { pathname } = useLocation();
const [config, setConfig] = useState({});
const [configError, setConfigError] = useState(null);
const config = useConfig();

const isReady = !!config.license_info;
const isSidebarVisible = useAuthorizedPath();
const [isAboutModalOpen, setIsAboutModalOpen] = useState(false);
const [isReady, setIsReady] = useState(false);

const sessionTimeoutId = useRef();
const sessionIntervalId = useRef();
Expand All @@ -99,7 +101,6 @@ function AppContainer({ i18n, navRouteConfig = [], children }) {

const handleAboutModalOpen = () => setIsAboutModalOpen(true);
const handleAboutModalClose = () => setIsAboutModalOpen(false);
const handleConfigErrorClose = () => setConfigError(null);
const handleSessionTimeout = () => setTimeoutWarning(true);

const handleLogout = useCallback(async () => {
Expand Down Expand Up @@ -137,31 +138,6 @@ function AppContainer({ i18n, navRouteConfig = [], children }) {
}
}, [handleLogout, timeRemaining]);

useEffect(() => {
const loadConfig = async () => {
if (config?.version) return;
try {
const [
{ data },
{
data: {
results: [me],
},
},
] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
setConfig({ ...data, me });
setIsReady(true);
} catch (err) {
if (err.response.status === 401) {
handleLogout();
return;
}
setConfigError(err);
}
};
loadConfig();
}, [config, pathname, handleLogout]);

const header = (
<PageHeader
showNavToggle
Expand All @@ -178,6 +154,23 @@ function AppContainer({ i18n, navRouteConfig = [], children }) {
/>
);

const simpleHeader = config.isLoading ? null : (
<PageHeader
logo={<BrandLogo />}
headerTools={
<PageHeaderTools>
<PageHeaderToolsGroup>
<PageHeaderToolsItem>
<Button onClick={handleLogout} variant="tertiary" ouiaId="logout">
{i18n._(t`Logout`)}
</Button>
</PageHeaderToolsItem>
</PageHeaderToolsGroup>
</PageHeaderTools>
}
/>
);

const sidebar = (
<PageSidebar
theme="dark"
Expand All @@ -200,23 +193,18 @@ function AppContainer({ i18n, navRouteConfig = [], children }) {

return (
<>
<Page isManagedSidebar header={header} sidebar={sidebar}>
{isReady && <ConfigProvider value={config}>{children}</ConfigProvider>}
<Page
isManagedSidebar={isSidebarVisible}
header={isSidebarVisible ? header : simpleHeader}
sidebar={isSidebarVisible && sidebar}
>
{isReady ? children : null}
</Page>
<About
version={config?.version}
isOpen={isAboutModalOpen}
onClose={handleAboutModalClose}
/>
<AlertModal
isOpen={configError}
variant="error"
title={i18n._(t`Error!`)}
onClose={handleConfigErrorClose}
>
{i18n._(t`Failed to retrieve configuration.`)}
<ErrorDetail error={configError} />
</AlertModal>
<AlertModal
ouiaId="session-expiration-modal"
title={i18n._(t`Your session is about to expire`)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
waitForElement,
} from '../../../testUtils/enzymeHelpers';
import { ConfigAPI, MeAPI, RootAPI } from '../../api';
import { useAuthorizedPath } from '../../contexts/Config';
import AppContainer from './AppContainer';

jest.mock('../../api');
Expand All @@ -19,10 +20,12 @@ describe('<AppContainer />', () => {
},
});
MeAPI.read.mockResolvedValue({ data: { results: [{}] } });
useAuthorizedPath.mockImplementation(() => true);
});

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

test('expected content is rendered', async () => {
Expand Down Expand Up @@ -77,7 +80,9 @@ describe('<AppContainer />', () => {

let wrapper;
await act(async () => {
wrapper = mountWithContexts(<AppContainer />);
wrapper = mountWithContexts(<AppContainer />, {
context: { config: { version } },
});
});

// open about dropdown menu
Expand Down
Loading

0 comments on commit 476cfd7

Please sign in to comment.