mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
moves some files to hooks in preparation for lingUI upgrade
This commit is contained in:
parent
33c3a6d89b
commit
d82f68c88e
@ -12,8 +12,8 @@ import {
|
||||
import { BrandName } from '../../variables';
|
||||
import brandLogoImg from './brand-logo.svg';
|
||||
|
||||
class About extends React.Component {
|
||||
static createSpeechBubble(version) {
|
||||
function About({ ansible_version, version, isOpen, onClose, i18n }) {
|
||||
const createSpeechBubble = () => {
|
||||
let text = `${BrandName} ${version}`;
|
||||
let top = '';
|
||||
let bottom = '';
|
||||
@ -28,31 +28,22 @@ class About extends React.Component {
|
||||
bottom = ` --${bottom}-- `;
|
||||
|
||||
return top + text + bottom;
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const speechBubble = createSpeechBubble();
|
||||
|
||||
this.createSpeechBubble = this.constructor.createSpeechBubble.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ansible_version, version, isOpen, onClose, i18n } = this.props;
|
||||
|
||||
const speechBubble = this.createSpeechBubble(version);
|
||||
|
||||
return (
|
||||
<AboutModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
productName={`Ansible ${BrandName}`}
|
||||
trademark={i18n._(t`Copyright 2019 Red Hat, Inc.`)}
|
||||
brandImageSrc={brandLogoImg}
|
||||
brandImageAlt={i18n._(t`Brand Image`)}
|
||||
>
|
||||
<pre>
|
||||
{speechBubble}
|
||||
{`
|
||||
return (
|
||||
<AboutModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
productName={`Ansible ${BrandName}`}
|
||||
trademark={i18n._(t`Copyright 2019 Red Hat, Inc.`)}
|
||||
brandImageSrc={brandLogoImg}
|
||||
brandImageAlt={i18n._(t`Brand Image`)}
|
||||
>
|
||||
<pre>
|
||||
{speechBubble}
|
||||
{`
|
||||
\\
|
||||
\\ ^__^
|
||||
(oo)\\_______
|
||||
@ -60,18 +51,17 @@ class About extends React.Component {
|
||||
||----w |
|
||||
|| ||
|
||||
`}
|
||||
</pre>
|
||||
<TextContent>
|
||||
<TextList component="dl">
|
||||
<TextListItem component="dt">
|
||||
{i18n._(t`Ansible Version`)}
|
||||
</TextListItem>
|
||||
<TextListItem component="dd">{ansible_version}</TextListItem>
|
||||
</TextList>
|
||||
</TextContent>
|
||||
</AboutModal>
|
||||
);
|
||||
}
|
||||
</pre>
|
||||
<TextContent>
|
||||
<TextList component="dl">
|
||||
<TextListItem component="dt">
|
||||
{i18n._(t`Ansible Version`)}
|
||||
</TextListItem>
|
||||
<TextListItem component="dd">{ansible_version}</TextListItem>
|
||||
</TextList>
|
||||
</TextContent>
|
||||
</AboutModal>
|
||||
);
|
||||
}
|
||||
|
||||
About.propTypes = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
@ -17,129 +17,100 @@ import { QuestionCircleIcon, UserIcon } from '@patternfly/react-icons';
|
||||
const DOCLINK =
|
||||
'https://docs.ansible.com/ansible-tower/latest/html/userguide/index.html';
|
||||
|
||||
class PageHeaderToolbar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isHelpOpen: false,
|
||||
isUserOpen: false,
|
||||
};
|
||||
function PageHeaderToolbar({
|
||||
isAboutDisabled,
|
||||
onAboutClick,
|
||||
onLogoutClick,
|
||||
loggedInUser,
|
||||
i18n,
|
||||
}) {
|
||||
const [isHelpOpen, setIsHelpOpen] = useState(false);
|
||||
const [isUserOpen, setIsUserOpen] = useState(false);
|
||||
|
||||
this.handleHelpSelect = this.handleHelpSelect.bind(this);
|
||||
this.handleHelpToggle = this.handleHelpToggle.bind(this);
|
||||
this.handleUserSelect = this.handleUserSelect.bind(this);
|
||||
this.handleUserToggle = this.handleUserToggle.bind(this);
|
||||
}
|
||||
const handleHelpSelect = () => {
|
||||
setIsHelpOpen(!isHelpOpen);
|
||||
};
|
||||
|
||||
handleHelpSelect() {
|
||||
const { isHelpOpen } = this.state;
|
||||
const handleUserSelect = () => {
|
||||
setIsUserOpen(!isUserOpen);
|
||||
};
|
||||
|
||||
this.setState({ isHelpOpen: !isHelpOpen });
|
||||
}
|
||||
|
||||
handleUserSelect() {
|
||||
const { isUserOpen } = this.state;
|
||||
|
||||
this.setState({ isUserOpen: !isUserOpen });
|
||||
}
|
||||
|
||||
handleHelpToggle(isOpen) {
|
||||
this.setState({ isHelpOpen: isOpen });
|
||||
}
|
||||
|
||||
handleUserToggle(isOpen) {
|
||||
this.setState({ isUserOpen: isOpen });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isHelpOpen, isUserOpen } = this.state;
|
||||
const {
|
||||
isAboutDisabled,
|
||||
onAboutClick,
|
||||
onLogoutClick,
|
||||
loggedInUser,
|
||||
i18n,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PageHeaderTools>
|
||||
<PageHeaderToolsGroup>
|
||||
<Tooltip position="left" content={<div>{i18n._(t`Info`)}</div>}>
|
||||
<PageHeaderToolsItem>
|
||||
<Dropdown
|
||||
isPlain
|
||||
isOpen={isHelpOpen}
|
||||
position={DropdownPosition.right}
|
||||
onSelect={this.handleHelpSelect}
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
onToggle={this.handleHelpToggle}
|
||||
aria-label={i18n._(t`Info`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={[
|
||||
<DropdownItem key="help" target="_blank" href={DOCLINK}>
|
||||
{i18n._(t`Help`)}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="about"
|
||||
component="button"
|
||||
isDisabled={isAboutDisabled}
|
||||
onClick={onAboutClick}
|
||||
>
|
||||
{i18n._(t`About`)}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</PageHeaderToolsItem>
|
||||
</Tooltip>
|
||||
<Tooltip position="left" content={<div>{i18n._(t`User`)}</div>}>
|
||||
<PageHeaderToolsItem>
|
||||
<Dropdown
|
||||
id="toolbar-user-dropdown"
|
||||
isPlain
|
||||
isOpen={isUserOpen}
|
||||
position={DropdownPosition.right}
|
||||
onSelect={this.handleUserSelect}
|
||||
toggle={
|
||||
<DropdownToggle onToggle={this.handleUserToggle}>
|
||||
<UserIcon />
|
||||
{loggedInUser && (
|
||||
<span style={{ marginLeft: '10px' }}>
|
||||
{loggedInUser.username}
|
||||
</span>
|
||||
)}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
key="user"
|
||||
href={
|
||||
loggedInUser
|
||||
? `/users/${loggedInUser.id}/details`
|
||||
: '/home'
|
||||
}
|
||||
>
|
||||
{i18n._(t`User Details`)}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="logout"
|
||||
component="button"
|
||||
onClick={onLogoutClick}
|
||||
id="logout-button"
|
||||
>
|
||||
{i18n._(t`Logout`)}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</PageHeaderToolsItem>
|
||||
</Tooltip>
|
||||
</PageHeaderToolsGroup>
|
||||
</PageHeaderTools>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PageHeaderTools>
|
||||
<PageHeaderToolsGroup>
|
||||
<Tooltip position="left" content={<div>{i18n._(t`Info`)}</div>}>
|
||||
<PageHeaderToolsItem>
|
||||
<Dropdown
|
||||
isPlain
|
||||
isOpen={isHelpOpen}
|
||||
position={DropdownPosition.right}
|
||||
onSelect={handleHelpSelect}
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
onToggle={setIsHelpOpen}
|
||||
aria-label={i18n._(t`Info`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={[
|
||||
<DropdownItem key="help" target="_blank" href={DOCLINK}>
|
||||
{i18n._(t`Help`)}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="about"
|
||||
component="button"
|
||||
isDisabled={isAboutDisabled}
|
||||
onClick={onAboutClick}
|
||||
>
|
||||
{i18n._(t`About`)}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</PageHeaderToolsItem>
|
||||
</Tooltip>
|
||||
<Tooltip position="left" content={<div>{i18n._(t`User`)}</div>}>
|
||||
<PageHeaderToolsItem>
|
||||
<Dropdown
|
||||
id="toolbar-user-dropdown"
|
||||
isPlain
|
||||
isOpen={isUserOpen}
|
||||
position={DropdownPosition.right}
|
||||
onSelect={handleUserSelect}
|
||||
toggle={
|
||||
<DropdownToggle onToggle={setIsUserOpen}>
|
||||
<UserIcon />
|
||||
{loggedInUser && (
|
||||
<span style={{ marginLeft: '10px' }}>
|
||||
{loggedInUser.username}
|
||||
</span>
|
||||
)}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
key="user"
|
||||
href={
|
||||
loggedInUser ? `/users/${loggedInUser.id}/details` : '/home'
|
||||
}
|
||||
>
|
||||
{i18n._(t`User Details`)}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="logout"
|
||||
component="button"
|
||||
onClick={onLogoutClick}
|
||||
id="logout-button"
|
||||
>
|
||||
{i18n._(t`Logout`)}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</PageHeaderToolsItem>
|
||||
</Tooltip>
|
||||
</PageHeaderToolsGroup>
|
||||
</PageHeaderTools>
|
||||
);
|
||||
}
|
||||
|
||||
PageHeaderToolbar.propTypes = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { withI18n } from '@lingui/react';
|
||||
@ -32,27 +32,15 @@ const Expandable = styled(PFExpandable)`
|
||||
}
|
||||
`;
|
||||
|
||||
class ErrorDetail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
function ErrorDetail({ error, i18n }) {
|
||||
const { response } = error;
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
this.state = {
|
||||
isExpanded: false,
|
||||
};
|
||||
const handleToggle = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
this.handleToggle = this.handleToggle.bind(this);
|
||||
this.renderNetworkError = this.renderNetworkError.bind(this);
|
||||
this.renderStack = this.renderStack.bind(this);
|
||||
}
|
||||
|
||||
handleToggle() {
|
||||
const { isExpanded } = this.state;
|
||||
this.setState({ isExpanded: !isExpanded });
|
||||
}
|
||||
|
||||
renderNetworkError() {
|
||||
const { error } = this.props;
|
||||
const { response } = error;
|
||||
const renderNetworkError = () => {
|
||||
const message = getErrorMessage(response);
|
||||
|
||||
return (
|
||||
@ -74,31 +62,25 @@ class ErrorDetail extends Component {
|
||||
</CardBody>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderStack() {
|
||||
const { error } = this.props;
|
||||
const renderStack = () => {
|
||||
return <CardBody>{error.stack}</CardBody>;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isExpanded } = this.state;
|
||||
const { error, i18n } = this.props;
|
||||
|
||||
return (
|
||||
<Expandable
|
||||
toggleText={i18n._(t`Details`)}
|
||||
onToggle={this.handleToggle}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<Card>
|
||||
{Object.prototype.hasOwnProperty.call(error, 'response')
|
||||
? this.renderNetworkError()
|
||||
: this.renderStack()}
|
||||
</Card>
|
||||
</Expandable>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Expandable
|
||||
toggleText={i18n._(t`Details`)}
|
||||
onToggle={handleToggle}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<Card>
|
||||
{Object.prototype.hasOwnProperty.call(error, 'response')
|
||||
? renderNetworkError()
|
||||
: renderStack()}
|
||||
</Card>
|
||||
</Expandable>
|
||||
);
|
||||
}
|
||||
|
||||
ErrorDetail.propTypes = {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
|
||||
import ErrorDetail from './ErrorDetail';
|
||||
@ -39,7 +40,7 @@ describe('ErrorDetail', () => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
wrapper.find('ExpandableSection').prop('onToggle')();
|
||||
act(() => wrapper.find('ExpandableSection').prop('onToggle')());
|
||||
wrapper.update();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,80 +1,56 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { Redirect, Link } from 'react-router-dom';
|
||||
import { PageSection, Card } from '@patternfly/react-core';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import useRequest from '../../util/useRequest';
|
||||
import { UnifiedJobsAPI } from '../../api';
|
||||
import ContentError from '../../components/ContentError';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
||||
|
||||
const NOT_FOUND = 'not found';
|
||||
|
||||
class JobTypeRedirect extends Component {
|
||||
static defaultProps = {
|
||||
view: 'output',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
job: null,
|
||||
isLoading: true,
|
||||
};
|
||||
this.loadJob = this.loadJob.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadJob();
|
||||
}
|
||||
|
||||
async loadJob() {
|
||||
const { id } = this.props;
|
||||
this.setState({ isLoading: true });
|
||||
try {
|
||||
function JobTypeRedirect({ id, path, view, i18n }) {
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
result: { job },
|
||||
request: loadJob,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const { data } = await UnifiedJobsAPI.read({ id });
|
||||
const job = data.results[0];
|
||||
this.setState({
|
||||
job,
|
||||
isLoading: false,
|
||||
error: job ? null : NOT_FOUND,
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
error,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
return { job: data };
|
||||
}, [id]),
|
||||
{ job: {} }
|
||||
);
|
||||
useEffect(() => {
|
||||
loadJob();
|
||||
}, [loadJob]);
|
||||
|
||||
render() {
|
||||
const { path, view, i18n } = this.props;
|
||||
const { error, job, isLoading } = this.state;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
{error === NOT_FOUND ? (
|
||||
<ContentError isNotFound>
|
||||
<Link to="/jobs">{i18n._(t`View all Jobs`)}</Link>
|
||||
</ContentError>
|
||||
) : (
|
||||
<ContentError error={error} />
|
||||
)}
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
if (isLoading) {
|
||||
// TODO show loading state
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const type = JOB_TYPE_URL_SEGMENTS[job.type];
|
||||
return <Redirect from={path} to={`/jobs/${type}/${job.id}/${view}`} />;
|
||||
if (error) {
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
{error === NOT_FOUND ? (
|
||||
<ContentError isNotFound>
|
||||
<Link to="/jobs">{i18n._(t`View all Jobs`)}</Link>
|
||||
</ContentError>
|
||||
) : (
|
||||
<ContentError error={error} />
|
||||
)}
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
if (isLoading) {
|
||||
// TODO show loading state
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const type = JOB_TYPE_URL_SEGMENTS[job.type];
|
||||
return <Redirect from={path} to={`/jobs/${type}/${job.id}/${view}`} />;
|
||||
}
|
||||
|
||||
JobTypeRedirect.defaultProps = {
|
||||
view: 'output',
|
||||
};
|
||||
export default withI18n()(JobTypeRedirect);
|
||||
|
||||
@ -1,21 +1,17 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import Breadcrumbs from '../../components/Breadcrumbs';
|
||||
|
||||
class ManagementJobs extends Component {
|
||||
render() {
|
||||
const { i18n } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Breadcrumbs
|
||||
breadcrumbConfig={{ '/management_jobs': i18n._(t`Management Jobs`) }}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
function ManagementJobs({ i18n }) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Breadcrumbs
|
||||
breadcrumbConfig={{ '/management_jobs': i18n._(t`Management Jobs`) }}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n()(ManagementJobs);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Route, withRouter, Switch } from 'react-router-dom';
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import { Route, withRouter, Switch, useRouteMatch } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
@ -10,28 +10,19 @@ import OrganizationsList from './OrganizationList/OrganizationList';
|
||||
import OrganizationAdd from './OrganizationAdd/OrganizationAdd';
|
||||
import Organization from './Organization';
|
||||
|
||||
class Organizations extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { i18n } = props;
|
||||
|
||||
this.state = {
|
||||
breadcrumbConfig: {
|
||||
'/organizations': i18n._(t`Organizations`),
|
||||
'/organizations/add': i18n._(t`Create New Organization`),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setBreadcrumbConfig = organization => {
|
||||
const { i18n } = this.props;
|
||||
function Organizations({ i18n }) {
|
||||
const match = useRouteMatch();
|
||||
const [breadcrumbConfig, setBreadcrumbConfig] = useState({
|
||||
'/organizations': i18n._(t`Organizations`),
|
||||
'/organizations/add': i18n._(t`Create New Organization`),
|
||||
});
|
||||
|
||||
const setBreadcrumb = organization => {
|
||||
if (!organization) {
|
||||
return;
|
||||
}
|
||||
|
||||
const breadcrumbConfig = {
|
||||
const breadcrumb = {
|
||||
'/organizations': i18n._(t`Organizations`),
|
||||
'/organizations/add': i18n._(t`Create New Organization`),
|
||||
[`/organizations/${organization.id}`]: `${organization.name}`,
|
||||
@ -43,38 +34,29 @@ class Organizations extends Component {
|
||||
t`Notifications`
|
||||
),
|
||||
};
|
||||
|
||||
this.setState({ breadcrumbConfig });
|
||||
setBreadcrumbConfig(breadcrumb);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { match } = this.props;
|
||||
const { breadcrumbConfig } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||
<Switch>
|
||||
<Route path={`${match.path}/add`}>
|
||||
<OrganizationAdd />
|
||||
</Route>
|
||||
<Route path={`${match.path}/:id`}>
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<Organization
|
||||
setBreadcrumb={this.setBreadcrumbConfig}
|
||||
me={me || {}}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
</Route>
|
||||
<Route path={`${match.path}`}>
|
||||
<OrganizationsList />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||
<Switch>
|
||||
<Route path={`${match.path}/add`}>
|
||||
<OrganizationAdd />
|
||||
</Route>
|
||||
<Route path={`${match.path}/:id`}>
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<Organization setBreadcrumb={setBreadcrumb} me={me || {}} />
|
||||
)}
|
||||
</Config>
|
||||
</Route>
|
||||
<Route path={`${match.path}`}>
|
||||
<OrganizationsList />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export { Organizations as _Organizations };
|
||||
|
||||
@ -11,6 +11,14 @@ import mockDetails from './data.project.json';
|
||||
import Project from './Project';
|
||||
|
||||
jest.mock('../../api');
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useRouteMatch: () => ({
|
||||
pathname: '/projects/1/details',
|
||||
url: '/projects/1',
|
||||
}),
|
||||
useParams: () => ({ id: 1 }),
|
||||
}));
|
||||
|
||||
const mockMe = {
|
||||
is_super_user: true,
|
||||
@ -50,7 +58,7 @@ describe('<Project />', () => {
|
||||
});
|
||||
const tabs = await waitForElement(
|
||||
wrapper,
|
||||
'.pf-c-tabs__item',
|
||||
'.pf-c-tabs__item-text',
|
||||
el => el.length === 6
|
||||
);
|
||||
expect(tabs.at(3).text()).toEqual('Notifications');
|
||||
@ -71,7 +79,7 @@ describe('<Project />', () => {
|
||||
});
|
||||
const tabs = await waitForElement(
|
||||
wrapper,
|
||||
'.pf-c-tabs__item',
|
||||
'.pf-c-tabs__item-text',
|
||||
el => el.length === 5
|
||||
);
|
||||
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
|
||||
@ -91,7 +99,6 @@ describe('<Project />', () => {
|
||||
<Project setBreadcrumb={() => {}} me={mockMe} />
|
||||
);
|
||||
});
|
||||
|
||||
const tabs = await waitForElement(
|
||||
wrapper,
|
||||
'.pf-c-tabs__item',
|
||||
@ -115,7 +122,6 @@ describe('<Project />', () => {
|
||||
<Project setBreadcrumb={() => {}} me={mockMe} />
|
||||
);
|
||||
});
|
||||
|
||||
const tabs = await waitForElement(
|
||||
wrapper,
|
||||
'.pf-c-tabs__item',
|
||||
|
||||
@ -1,89 +1,21 @@
|
||||
/* eslint react/no-unused-state: 0 */
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter, Redirect } from 'react-router-dom';
|
||||
import React, { useState } from 'react';
|
||||
import { withRouter, Redirect, useHistory } from 'react-router-dom';
|
||||
|
||||
import { CardBody } from '../../../components/Card';
|
||||
import ContentError from '../../../components/ContentError';
|
||||
import ContentLoading from '../../../components/ContentLoading';
|
||||
import { JobTemplatesAPI } from '../../../api';
|
||||
import { JobTemplate } from '../../../types';
|
||||
import { getAddedAndRemoved } from '../../../util/lists';
|
||||
import JobTemplateForm from '../shared/JobTemplateForm';
|
||||
|
||||
class JobTemplateEdit extends Component {
|
||||
static propTypes = {
|
||||
template: JobTemplate.isRequired,
|
||||
};
|
||||
function JobTemplateEdit({ template }) {
|
||||
const { id, type } = template;
|
||||
const history = useHistory();
|
||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const detailsUrl = `/templates/${type}/${id}/details`;
|
||||
|
||||
this.state = {
|
||||
hasContentLoading: true,
|
||||
contentError: null,
|
||||
formSubmitError: null,
|
||||
relatedCredentials: [],
|
||||
relatedProjectPlaybooks: [],
|
||||
};
|
||||
|
||||
const {
|
||||
template: { id, type },
|
||||
} = props;
|
||||
this.detailsUrl = `/templates/${type}/${id}/details`;
|
||||
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.loadRelatedCredentials = this.loadRelatedCredentials.bind(this);
|
||||
this.submitLabels = this.submitLabels.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadRelated();
|
||||
}
|
||||
|
||||
async loadRelated() {
|
||||
this.setState({ contentError: null, hasContentLoading: true });
|
||||
try {
|
||||
const [relatedCredentials] = await this.loadRelatedCredentials();
|
||||
this.setState({
|
||||
relatedCredentials,
|
||||
});
|
||||
} catch (contentError) {
|
||||
this.setState({ contentError });
|
||||
} finally {
|
||||
this.setState({ hasContentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
async loadRelatedCredentials() {
|
||||
const {
|
||||
template: { id },
|
||||
} = this.props;
|
||||
const params = {
|
||||
page: 1,
|
||||
page_size: 200,
|
||||
order_by: 'name',
|
||||
};
|
||||
try {
|
||||
const {
|
||||
data: { results: credentials = [] },
|
||||
} = await JobTemplatesAPI.readCredentials(id, params);
|
||||
return credentials;
|
||||
} catch (err) {
|
||||
if (err.status !== 403) throw err;
|
||||
|
||||
this.setState({ hasRelatedCredentialAccess: false });
|
||||
const {
|
||||
template: {
|
||||
summary_fields: { credentials = [] },
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit(values) {
|
||||
const { template, history } = this.props;
|
||||
const handleSubmit = async values => {
|
||||
const {
|
||||
labels,
|
||||
instanceGroups,
|
||||
@ -95,25 +27,23 @@ class JobTemplateEdit extends Component {
|
||||
...remainingValues
|
||||
} = values;
|
||||
|
||||
this.setState({ formSubmitError: null });
|
||||
setFormSubmitError(null);
|
||||
remainingValues.project = values.project.id;
|
||||
remainingValues.webhook_credential = webhook_credential?.id || null;
|
||||
try {
|
||||
await JobTemplatesAPI.update(template.id, remainingValues);
|
||||
await Promise.all([
|
||||
this.submitLabels(labels, template?.organization),
|
||||
this.submitInstanceGroups(instanceGroups, initialInstanceGroups),
|
||||
this.submitCredentials(credentials),
|
||||
submitLabels(labels, template?.organization),
|
||||
submitInstanceGroups(instanceGroups, initialInstanceGroups),
|
||||
submitCredentials(credentials),
|
||||
]);
|
||||
history.push(this.detailsUrl);
|
||||
} catch (error) {
|
||||
this.setState({ formSubmitError: error });
|
||||
history.push(detailsUrl);
|
||||
} catch (err) {
|
||||
setFormSubmitError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async submitLabels(labels = [], orgId) {
|
||||
const { template } = this.props;
|
||||
};
|
||||
|
||||
const submitLabels = async (labels = [], orgId) => {
|
||||
const { added, removed } = getAddedAndRemoved(
|
||||
template.summary_fields.labels.results,
|
||||
labels
|
||||
@ -131,10 +61,9 @@ class JobTemplateEdit extends Component {
|
||||
...associationPromises,
|
||||
]);
|
||||
return results;
|
||||
}
|
||||
};
|
||||
|
||||
async submitInstanceGroups(groups, initialGroups) {
|
||||
const { template } = this.props;
|
||||
const submitInstanceGroups = async (groups, initialGroups) => {
|
||||
const { added, removed } = getAddedAndRemoved(initialGroups, groups);
|
||||
const disassociatePromises = await removed.map(group =>
|
||||
JobTemplatesAPI.disassociateInstanceGroup(template.id, group.id)
|
||||
@ -143,10 +72,9 @@ class JobTemplateEdit extends Component {
|
||||
JobTemplatesAPI.associateInstanceGroup(template.id, group.id)
|
||||
);
|
||||
return Promise.all([...disassociatePromises, ...associatePromises]);
|
||||
}
|
||||
};
|
||||
|
||||
async submitCredentials(newCredentials) {
|
||||
const { template } = this.props;
|
||||
const submitCredentials = async newCredentials => {
|
||||
const { added, removed } = getAddedAndRemoved(
|
||||
template.summary_fields.credentials,
|
||||
newCredentials
|
||||
@ -160,55 +88,32 @@ class JobTemplateEdit extends Component {
|
||||
);
|
||||
const associatePromise = Promise.all(associateCredentials);
|
||||
return Promise.all([disassociatePromise, associatePromise]);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
history.push(detailsUrl);
|
||||
};
|
||||
|
||||
const canEdit = template.summary_fields.user_capabilities.edit;
|
||||
|
||||
if (!canEdit) {
|
||||
return <Redirect to={detailsUrl} />;
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
const { history } = this.props;
|
||||
history.push(this.detailsUrl);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { template } = this.props;
|
||||
const {
|
||||
contentError,
|
||||
formSubmitError,
|
||||
hasContentLoading,
|
||||
relatedProjectPlaybooks,
|
||||
} = this.state;
|
||||
const canEdit = template.summary_fields.user_capabilities.edit;
|
||||
|
||||
if (hasContentLoading) {
|
||||
return (
|
||||
<CardBody>
|
||||
<ContentLoading />
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
if (contentError) {
|
||||
return (
|
||||
<CardBody>
|
||||
<ContentError error={contentError} />
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
if (!canEdit) {
|
||||
return <Redirect to={this.detailsUrl} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
<JobTemplateForm
|
||||
template={template}
|
||||
handleCancel={this.handleCancel}
|
||||
handleSubmit={this.handleSubmit}
|
||||
relatedProjectPlaybooks={relatedProjectPlaybooks}
|
||||
submitError={formSubmitError}
|
||||
/>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CardBody>
|
||||
<JobTemplateForm
|
||||
template={template}
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
submitError={formSubmitError}
|
||||
/>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
JobTemplateForm.propTypes = {
|
||||
template: JobTemplate.isRequired,
|
||||
};
|
||||
|
||||
export default withRouter(JobTemplateEdit);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Route, withRouter, Switch } from 'react-router-dom';
|
||||
import { Route, withRouter, Switch, useRouteMatch } from 'react-router-dom';
|
||||
import { PageSection } from '@patternfly/react-core';
|
||||
|
||||
import { Config } from '../../contexts/Config';
|
||||
@ -12,28 +12,21 @@ import WorkflowJobTemplate from './WorkflowJobTemplate';
|
||||
import JobTemplateAdd from './JobTemplateAdd';
|
||||
import WorkflowJobTemplateAdd from './WorkflowJobTemplateAdd';
|
||||
|
||||
class Templates extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { i18n } = this.props;
|
||||
function Templates({ i18n }) {
|
||||
const match = useRouteMatch();
|
||||
const [breadcrumbConfig, setBreadcrumbs] = useState({
|
||||
'/templates': i18n._(t`Templates`),
|
||||
'/templates/job_template/add': i18n._(t`Create New Job Template`),
|
||||
'/templates/workflow_job_template/add': i18n._(
|
||||
t`Create New Workflow Template`
|
||||
),
|
||||
});
|
||||
|
||||
this.state = {
|
||||
breadcrumbConfig: {
|
||||
'/templates': i18n._(t`Templates`),
|
||||
'/templates/job_template/add': i18n._(t`Create New Job Template`),
|
||||
'/templates/workflow_job_template/add': i18n._(
|
||||
t`Create New Workflow Template`
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setBreadCrumbConfig = (template, schedule) => {
|
||||
const { i18n } = this.props;
|
||||
const setBreadCrumbConfig = (template, schedule) => {
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
const breadcrumbConfig = {
|
||||
const breadcrumb = {
|
||||
'/templates': i18n._(t`Templates`),
|
||||
'/templates/job_template/add': i18n._(t`Create New Job Template`),
|
||||
'/templates/workflow_job_template/add': i18n._(
|
||||
@ -73,62 +66,55 @@ class Templates extends Component {
|
||||
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
|
||||
schedule.id}/edit`]: i18n._(t`Edit Details`),
|
||||
};
|
||||
this.setState({ breadcrumbConfig });
|
||||
setBreadcrumbs(breadcrumb);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { match, history, location } = this.props;
|
||||
const { breadcrumbConfig } = this.state;
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||
<Switch>
|
||||
<Route path={`${match.path}/job_template/add`}>
|
||||
<JobTemplateAdd />
|
||||
</Route>
|
||||
<Route path={`${match.path}/workflow_job_template/add`}>
|
||||
<WorkflowJobTemplateAdd />
|
||||
</Route>
|
||||
<Route
|
||||
path={`${match.path}/job_template/:id`}
|
||||
render={({ match: newRouteMatch }) => (
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<Template
|
||||
history={history}
|
||||
location={location}
|
||||
setBreadcrumb={this.setBreadCrumbConfig}
|
||||
me={me || {}}
|
||||
match={newRouteMatch}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}/workflow_job_template/:id`}
|
||||
render={({ match: newRouteMatch }) => (
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<WorkflowJobTemplate
|
||||
location={location}
|
||||
setBreadcrumb={this.setBreadCrumbConfig}
|
||||
me={me || {}}
|
||||
match={newRouteMatch}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
)}
|
||||
/>
|
||||
<Route path={`${match.path}`}>
|
||||
<PageSection>
|
||||
<TemplateList />
|
||||
</PageSection>
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||
<Switch>
|
||||
<Route path={`${match.path}/job_template/add`}>
|
||||
<JobTemplateAdd />
|
||||
</Route>
|
||||
<Route path={`${match.path}/workflow_job_template/add`}>
|
||||
<WorkflowJobTemplateAdd />
|
||||
</Route>
|
||||
<Route
|
||||
path={`${match.path}/job_template/:id`}
|
||||
render={({ match: newRouteMatch }) => (
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<Template
|
||||
setBreadcrumb={setBreadCrumbConfig}
|
||||
me={me || {}}
|
||||
match={newRouteMatch}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}/workflow_job_template/:id`}
|
||||
render={({ match: newRouteMatch }) => (
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<WorkflowJobTemplate
|
||||
setBreadcrumb={setBreadCrumbConfig}
|
||||
me={me || {}}
|
||||
match={newRouteMatch}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
)}
|
||||
/>
|
||||
<Route path={`${match.path}`}>
|
||||
<PageSection>
|
||||
<TemplateList />
|
||||
</PageSection>
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { Templates as _Templates };
|
||||
|
||||
@ -1,27 +1,24 @@
|
||||
import React, { Component } from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { mountWithContexts, waitForElement } from './enzymeHelpers';
|
||||
import { Config } from '../src/contexts/Config';
|
||||
|
||||
describe('mountWithContexts', () => {
|
||||
describe('injected I18nProvider', () => {
|
||||
test('should mount and render', () => {
|
||||
const Child = withI18n()(({ i18n }) => (
|
||||
const Child = () => (
|
||||
<div>
|
||||
<span>{i18n._(t`Text content`)}</span>
|
||||
<span>Text content</span>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
const wrapper = mountWithContexts(<Child />);
|
||||
expect(wrapper.find('div')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should mount and render deeply nested consumer', () => {
|
||||
const Child = withI18n()(({ i18n }) => (
|
||||
<div>{i18n._(t`Text content`)}</div>
|
||||
));
|
||||
const Child = () => <div>Text content</div>;
|
||||
const Parent = () => <Child />;
|
||||
const wrapper = mountWithContexts(<Parent />);
|
||||
expect(wrapper.find('Parent')).toMatchSnapshot();
|
||||
@ -146,7 +143,9 @@ describe('waitForElement', () => {
|
||||
} catch (err) {
|
||||
error = err;
|
||||
} finally {
|
||||
expect(error.message).toContain('Expected condition for <#does-not-exist> not met');
|
||||
expect(error.message).toContain(
|
||||
'Expected condition for <#does-not-exist> not met'
|
||||
);
|
||||
expect(error.message).toContain('el.length === 1');
|
||||
done();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user