Skip to content

Commit

Permalink
Add subscription wizard and redirect logic
Browse files Browse the repository at this point in the history
  • Loading branch information
marshmalien committed Apr 6, 2021
1 parent 868f680 commit 440bdee
Show file tree
Hide file tree
Showing 45 changed files with 2,495 additions and 279 deletions.
3 changes: 2 additions & 1 deletion awx/ui_next/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@
"theme",
"gridColumns",
"rows",
"href"
"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 440bdee

Please sign in to comment.