Merge pull request #8526 from mabashian/convert-WorkflowJobTemplatejsx-functional

Convert WorkflowJobTemplate.jsx to functional component

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-11-04 01:14:45 +00:00
committed by GitHub
3 changed files with 465 additions and 418 deletions

View File

@@ -1,317 +1,265 @@
import React, { Component } from 'react'; import React, { useEffect, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { CaretLeftIcon } from '@patternfly/react-icons'; import { CaretLeftIcon } from '@patternfly/react-icons';
import { Card, PageSection } from '@patternfly/react-core'; import { Card, PageSection } from '@patternfly/react-core';
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom'; import {
Switch,
Route,
Redirect,
Link,
useLocation,
useParams,
useRouteMatch,
} from 'react-router-dom';
import RoutedTabs from '../../components/RoutedTabs';
import useRequest from '../../util/useRequest';
import AppendBody from '../../components/AppendBody'; import AppendBody from '../../components/AppendBody';
import ContentError from '../../components/ContentError'; import ContentError from '../../components/ContentError';
import FullPage from '../../components/FullPage'; import FullPage from '../../components/FullPage';
import JobList from '../../components/JobList'; import JobList from '../../components/JobList';
import RoutedTabs from '../../components/RoutedTabs';
import { Schedules } from '../../components/Schedule';
import ContentLoading from '../../components/ContentLoading';
import { ResourceAccessList } from '../../components/ResourceAccessList';
import NotificationList from '../../components/NotificationList'; import NotificationList from '../../components/NotificationList';
import { import { Schedules } from '../../components/Schedule';
WorkflowJobTemplatesAPI, import { ResourceAccessList } from '../../components/ResourceAccessList';
CredentialsAPI,
OrganizationsAPI,
} from '../../api';
import WorkflowJobTemplateDetail from './WorkflowJobTemplateDetail'; import WorkflowJobTemplateDetail from './WorkflowJobTemplateDetail';
import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit'; import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
import { Visualizer } from './WorkflowJobTemplateVisualizer'; import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
import TemplateSurvey from './TemplateSurvey'; import TemplateSurvey from './TemplateSurvey';
import { Visualizer } from './WorkflowJobTemplateVisualizer';
class WorkflowJobTemplate extends Component { function WorkflowJobTemplate({ i18n, me, setBreadcrumb }) {
constructor(props) { const location = useLocation();
super(props); const { id: templateId } = useParams();
const match = useRouteMatch();
this.state = { const {
contentError: null, result: { isNotifAdmin, template },
hasContentLoading: true, isLoading: hasRolesandTemplateLoading,
template: null, error: rolesAndTemplateError,
isNotifAdmin: false, request: loadTemplateAndRoles,
}; } = useRequest(
this.createSchedule = this.createSchedule.bind(this); useCallback(async () => {
this.loadTemplate = this.loadTemplate.bind(this); const [{ data }, actions, notifAdminRes] = await Promise.all([
this.loadSchedules = this.loadSchedules.bind(this); WorkflowJobTemplatesAPI.readDetail(templateId),
this.loadScheduleOptions = this.loadScheduleOptions.bind(this); WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions(templateId),
} OrganizationsAPI.read({
page_size: 1,
async componentDidMount() { role_level: 'notification_admin_role',
await this.loadTemplate(); }),
}
async componentDidUpdate(prevProps) {
const { location } = this.props;
if (location !== prevProps.location) {
await this.loadTemplate();
}
}
async loadTemplate() {
const { setBreadcrumb, match } = this.props;
const { id } = match.params;
this.setState({ contentError: null });
try {
const [
{ data },
{
data: { actions },
},
] = await Promise.all([
WorkflowJobTemplatesAPI.readDetail(id),
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions(id),
]); ]);
let webhookKey;
if (actions.PUT) { if (actions.data.actions.PUT) {
if (data?.webhook_service && data?.related?.webhook_key) { if (data.webhook_service && data?.related?.webhook_key) {
webhookKey = await WorkflowJobTemplatesAPI.readWebhookKey(id); const {
data: { webhook_key },
} = await WorkflowJobTemplatesAPI.readWebhookKey(templateId);
data.webhook_key = webhook_key;
} }
} }
if (data?.summary_fields?.webhook_credential) {
const {
data: {
summary_fields: {
credential_type: { name },
},
},
} = await CredentialsAPI.readDetail(
data.summary_fields.webhook_credential.id
);
data.summary_fields.webhook_credential.kind = name;
}
const notifAdminRes = await OrganizationsAPI.read({
page_size: 1,
role_level: 'notification_admin_role',
});
setBreadcrumb(data); setBreadcrumb(data);
this.setState({
template: { ...data, webhook_key: webhookKey?.data.webhook_key }, return {
template: data,
isNotifAdmin: notifAdminRes.data.results.length > 0, isNotifAdmin: notifAdminRes.data.results.length > 0,
}); };
} catch (err) { }, [setBreadcrumb, templateId]),
this.setState({ contentError: err }); { isNotifAdmin: false, template: null }
} finally { );
this.setState({ hasContentLoading: false }); useEffect(() => {
} loadTemplateAndRoles();
} }, [loadTemplateAndRoles, location.pathname]);
createSchedule(data) { const createSchedule = data => {
const { template } = this.state; return WorkflowJobTemplatesAPI.createSchedule(templateId, data);
return WorkflowJobTemplatesAPI.createSchedule(template.id, data); };
}
loadScheduleOptions() { const loadScheduleOptions = () => {
const { template } = this.state; return WorkflowJobTemplatesAPI.readScheduleOptions(templateId);
return WorkflowJobTemplatesAPI.readScheduleOptions(template.id); };
}
loadSchedules(params) { const loadSchedules = params => {
const { template } = this.state; return WorkflowJobTemplatesAPI.readSchedules(templateId, params);
return WorkflowJobTemplatesAPI.readSchedules(template.id, params); };
}
render() { const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
const { i18n, me, location, match, setBreadcrumb } = this.props; const canAddAndEditSurvey =
const { template?.summary_fields?.user_capabilities.edit ||
contentError, template?.summary_fields?.user_capabilities.delete;
hasContentLoading,
template,
isNotifAdmin,
} = this.state;
const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin; const tabsArray = [
const canToggleNotifications = isNotifAdmin; {
const canAddAndEditSurvey = name: (
template?.summary_fields?.user_capabilities.edit || <>
template?.summary_fields?.user_capabilities.delete; <CaretLeftIcon />
{i18n._(t`Back to Templates`)}
const tabsArray = [ </>
{ ),
name: ( link: `/templates`,
<> id: 99,
<CaretLeftIcon /> },
{i18n._(t`Back to Templates`)} { name: i18n._(t`Details`), link: `${match.url}/details` },
</> { name: i18n._(t`Access`), link: `${match.url}/access` },
), ];
link: `/templates`,
id: 99,
},
{ name: i18n._(t`Details`), link: `${match.url}/details` },
{ name: i18n._(t`Access`), link: `${match.url}/access` },
];
if (canSeeNotificationsTab) {
tabsArray.push({
name: i18n._(t`Notifications`),
link: `${match.url}/notifications`,
});
}
if (template) {
tabsArray.push({
name: i18n._(t`Schedules`),
link: `${match.url}/schedules`,
});
}
if (canSeeNotificationsTab) {
tabsArray.push({ tabsArray.push({
name: i18n._(t`Notifications`),
link: `${match.url}/notifications`,
});
}
if (template) {
tabsArray.push({
name: i18n._(t`Schedules`),
link: `${match.url}/schedules`,
});
}
tabsArray.push(
{
name: i18n._(t`Visualizer`), name: i18n._(t`Visualizer`),
link: `${match.url}/visualizer`, link: `${match.url}/visualizer`,
}); },
tabsArray.push({ {
name: i18n._(t`Completed Jobs`), name: i18n._(t`Completed Jobs`),
link: `${match.url}/completed_jobs`, link: `${match.url}/completed_jobs`,
}); },
tabsArray.push({ {
name: canAddAndEditSurvey ? i18n._(t`Survey`) : i18n._(t`View Survey`), name: canAddAndEditSurvey ? i18n._(t`Survey`) : i18n._(t`View Survey`),
link: `${match.url}/survey`, link: `${match.url}/survey`,
});
tabsArray.forEach((tab, n) => {
tab.id = n;
});
if (hasContentLoading) {
return (
<PageSection>
<Card>
<ContentLoading />
</Card>
</PageSection>
);
} }
);
if (contentError) { tabsArray.forEach((tab, n) => {
return ( tab.id = n;
<PageSection> });
<Card>
<ContentError error={contentError}>
{contentError.response.status === 404 && (
<span>
{i18n._(t`Template not found.`)}{' '}
<Link to="/templates">{i18n._(t`View all Templates.`)}</Link>
</span>
)}
</ContentError>
</Card>
</PageSection>
);
}
let showCardHeader = true; let showCardHeader = true;
if ( if (
location.pathname.endsWith('edit') || location.pathname.endsWith('edit') ||
location.pathname.includes('schedules/') location.pathname.includes('schedules/')
) { ) {
showCardHeader = false; showCardHeader = false;
} }
const contentError = rolesAndTemplateError;
if (!hasRolesandTemplateLoading && contentError) {
return ( return (
<PageSection> <PageSection>
<Card> <Card>
{showCardHeader && <RoutedTabs tabsArray={tabsArray} />} <ContentError error={contentError}>
<Switch> {contentError.response.status === 404 && (
<Redirect <span>
from="/templates/workflow_job_template/:id" {i18n._(t`Template not found.`)}{' '}
to="/templates/workflow_job_template/:id/details" <Link to="/templates">{i18n._(t`View all Templates.`)}</Link>
exact </span>
/>
{template && (
<Route
key="wfjt-details"
path="/templates/workflow_job_template/:id/details"
>
<WorkflowJobTemplateDetail template={template} />
</Route>
)}
{template && (
<Route path="/templates/workflow_job_template/:id/access">
<ResourceAccessList
resource={template}
apiModel={WorkflowJobTemplatesAPI}
/>
</Route>
)}
{canSeeNotificationsTab && (
<Route path="/templates/workflow_job_template/:id/notifications">
<NotificationList
id={Number(match.params.id)}
canToggleNotifications={canToggleNotifications}
apiModel={WorkflowJobTemplatesAPI}
showApprovalsToggle
/>
</Route>
)}
{template && (
<Route
key="wfjt-edit"
path="/templates/workflow_job_template/:id/edit"
>
<WorkflowJobTemplateEdit template={template} />
</Route>
)}
{template && (
<Route
key="wfjt-visualizer"
path="/templates/workflow_job_template/:id/visualizer"
>
<AppendBody>
<FullPage>
<Visualizer template={template} />
</FullPage>
</AppendBody>
</Route>
)}
{template?.id && (
<Route path="/templates/workflow_job_template/:id/completed_jobs">
<JobList
defaultParams={{
workflow_job__workflow_job_template: template.id,
}}
/>
</Route>
)}
{template?.id && (
<Route path="/templates/workflow_job_template/:id/schedules">
<Schedules
setBreadcrumb={setBreadcrumb}
unifiedJobTemplate={template}
createSchedule={this.createSchedule}
loadSchedules={this.loadSchedules}
loadScheduleOptions={this.loadScheduleOptions}
/>
</Route>
)}
{template && (
<Route path="/templates/:templateType/:id/survey">
<TemplateSurvey
template={template}
canEdit={canAddAndEditSurvey}
/>
</Route>
)} )}
</ContentError>
</Card>
</PageSection>
);
}
return (
<PageSection>
<Card>
{showCardHeader && <RoutedTabs tabsArray={tabsArray} />}
<Switch>
<Redirect
from="/templates/:templateType/:id"
to="/templates/:templateType/:id/details"
exact
/>
{template && (
<Route key="details" path="/templates/:templateType/:id/details">
<WorkflowJobTemplateDetail template={template} />
</Route>
)}
{template && (
<Route key="edit" path="/templates/:templateType/:id/edit">
<WorkflowJobTemplateEdit template={template} />
</Route>
)}
{template && (
<Route key="access" path="/templates/:templateType/:id/access">
<ResourceAccessList
resource={template}
apiModel={WorkflowJobTemplatesAPI}
/>
</Route>
)}
{template && (
<Route
key="schedules"
path="/templates/:templateType/:id/schedules"
>
<Schedules
createSchedule={createSchedule}
setBreadcrumb={setBreadcrumb}
unifiedJobTemplate={template}
loadSchedules={loadSchedules}
loadScheduleOptions={loadScheduleOptions}
/>
</Route>
)}
{canSeeNotificationsTab && (
<Route path="/templates/:templateType/:id/notifications">
<NotificationList
id={Number(templateId)}
canToggleNotifications={isNotifAdmin}
apiModel={WorkflowJobTemplatesAPI}
/>
</Route>
)}
{template && (
<Route
key="wfjt-visualizer"
path="/templates/workflow_job_template/:id/visualizer"
>
<AppendBody>
<FullPage>
<Visualizer template={template} />
</FullPage>
</AppendBody>
</Route>
)}
{template?.id && (
<Route path="/templates/:templateType/:id/completed_jobs">
<JobList
defaultParams={{
workflow_job__workflow_job_template: template.id,
}}
/>
</Route>
)}
{template && (
<Route path="/templates/:templateType/:id/survey">
<TemplateSurvey
template={template}
canEdit={canAddAndEditSurvey}
/>
</Route>
)}
{!hasRolesandTemplateLoading && (
<Route key="not-found" path="*"> <Route key="not-found" path="*">
<ContentError isNotFound> <ContentError isNotFound>
{match.params.id && ( {match.params.id && (
<Link <Link
to={`/templates/workflow_job_template/${match.params.id}/details`} to={`/templates/${match.params.templateType}/${match.params.id}/details`}
> >
{i18n._(t`View Template Details`)} {i18n._(t`View Template Details`)}
</Link> </Link>
)} )}
</ContentError> </ContentError>
</Route> </Route>
</Switch> )}
</Card> </Switch>
</PageSection> </Card>
); </PageSection>
} );
} }
export { WorkflowJobTemplate as _WorkflowJobTemplate }; export { WorkflowJobTemplate as _WorkflowJobTemplate };
export default withI18n()(withRouter(WorkflowJobTemplate)); export default withI18n()(WorkflowJobTemplate);

View File

@@ -1,192 +1,196 @@
import React from 'react'; import React from 'react';
import { Route } from 'react-router-dom';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
import { import {
mountWithContexts, mountWithContexts,
waitForElement, waitForElement,
} from '../../../testUtils/enzymeHelpers'; } from '../../../testUtils/enzymeHelpers';
import WorkflowJobTemplate from './WorkflowJobTemplate'; import WorkflowJobTemplate from './WorkflowJobTemplate';
import { sleep } from '../../../testUtils/testUtils'; import mockWorkflowJobTemplateData from './shared/data.workflow_job_template.json';
import {
WorkflowJobTemplatesAPI,
CredentialsAPI,
OrganizationsAPI,
} from '../../api';
jest.mock('../../api/models/WorkflowJobTemplates'); jest.mock('../../api/models/WorkflowJobTemplates');
jest.mock('../../api/models/Credentials');
jest.mock('../../api/models/Organizations'); jest.mock('../../api/models/Organizations');
describe('<WorkflowJobTemplate/>', () => { const mockMe = {
const mockMe = { is_super_user: true,
is_super_user: true, is_system_auditor: false,
is_system_auditor: false, };
}; describe('<WorkflowJobTemplate />', () => {
let wrapper; let wrapper;
let history; beforeEach(() => {
beforeAll(() => {
WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({ WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({
data: { data: mockWorkflowJobTemplateData,
id: 1,
name: 'Foo',
description: 'Bar',
created: '2015-07-07T17:21:26.429745Z',
modified: '2019-08-11T19:47:37.980466Z',
extra_vars: '',
webhook_service: 'github',
summary_fields: {
webhook_credential: { id: 1234567, name: 'Foo Webhook Credential' },
created_by: { id: 1, username: 'Athena' },
modified_by: { id: 1, username: 'Apollo' },
recent_jobs: [
{ id: 1, status: 'run' },
{ id: 2, status: 'run' },
{ id: 3, status: 'run' },
],
labels: {
results: [
{ name: 'Label 1', id: 1 },
{ name: 'Label 2', id: 2 },
{ name: 'Label 3', id: 3 },
],
},
user_capabilities: {},
},
related: {
webhook_key: '/api/v2/workflow_job_templates/57/webhook_key/',
},
},
}); });
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValue({
WorkflowJobTemplatesAPI.readWebhookKey.mockResolvedValue({
data: { webhook_key: 'WebHook Key' },
});
CredentialsAPI.readDetail.mockResolvedValue({
data: { data: {
summary_fields: { actions: { PUT: true },
credential_type: { name: 'Github Personal Access Token', id: 1 },
},
}, },
}); });
OrganizationsAPI.read.mockResolvedValue({ OrganizationsAPI.read.mockResolvedValue({
data: { results: [{ id: 1, name: 'Org Foo' }] }, data: {
count: 1,
next: null,
previous: null,
results: [
{
id: 1,
},
],
},
});
WorkflowJobTemplatesAPI.readWebhookKey.mockResolvedValue({
data: {
webhook_key: 'key',
},
}); });
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount(); wrapper.unmount();
}); });
describe('User can PUT', () => { test('initially renders succesfully', async () => {
beforeEach(async () => { await act(async () => {
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValue({ wrapper = mountWithContexts(
data: { actions: { PUT: {} } }, <WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
}); );
history = createMemoryHistory({
initialEntries: ['/templates/workflow_job_template/1/details'],
});
await act(async () => {
wrapper = mountWithContexts(
<Route
path="/templates/workflow_job_template/:id/details"
component={() => (
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
)}
/>,
{
context: {
router: {
history,
},
},
}
);
});
});
test('calls api to get workflow job template data', async () => {
expect(wrapper.find('WorkflowJobTemplate').length).toBe(1);
expect(WorkflowJobTemplatesAPI.readDetail).toBeCalledWith('1');
wrapper.update();
await sleep(0);
expect(WorkflowJobTemplatesAPI.readWebhookKey).toBeCalledWith('1');
expect(
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions
).toBeCalled();
expect(CredentialsAPI.readDetail).toBeCalledWith(1234567);
expect(OrganizationsAPI.read).toBeCalledWith({
page_size: 1,
role_level: 'notification_admin_role',
});
});
test('renders proper tabs', async () => {
const tabs = [
'Details',
'Access',
'Notifications',
'Schedules',
'Visualizer',
'Completed Jobs',
'Survey',
];
waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
wrapper.update();
wrapper.find('TabContainer').forEach(tc => {
tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
});
});
test('Does not render Notifications tab', async () => {
OrganizationsAPI.read.mockResolvedValue({
data: { results: [] },
});
const tabs = [
'Details',
'Access',
'Schedules',
'Visualizer',
'Completed Jobs',
'Survey',
];
waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
wrapper.update();
wrapper.find('TabContainer').forEach(tc => {
tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
});
}); });
}); });
describe('User cannot PUT', () => { test('When component mounts API is called and the response is put in state', async () => {
beforeEach(async () => { await act(async () => {
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValueOnce( wrapper = mountWithContexts(
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
);
});
expect(WorkflowJobTemplatesAPI.readDetail).toBeCalled();
expect(OrganizationsAPI.read).toBeCalled();
});
test('notifications tab shown for admins', async done => {
await act(async () => {
wrapper = mountWithContexts(
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
);
});
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
el => el.length === 8
);
expect(tabs.at(3).text()).toEqual('Notifications');
done();
});
test('notifications tab hidden with reduced permissions', async done => {
OrganizationsAPI.read.mockResolvedValue({
data: {
count: 0,
next: null,
previous: null,
results: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
);
});
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
el => el.length === 7
);
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
done();
});
test('should show content error when user attempts to navigate to erroneous route', async () => {
const history = createMemoryHistory({
initialEntries: ['/templates/workflow_job_template/1/foobar'],
});
await act(async () => {
wrapper = mountWithContexts(
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />,
{ {
data: { actions: {} }, context: {
router: {
history,
route: {
location: history.location,
match: {
params: { id: 1 },
url: '/templates/workflow_job_template/1/foobar',
path: '/templates/workflow_job_template/1/foobar',
},
},
},
},
} }
); );
history = createMemoryHistory({ });
initialEntries: ['/templates/workflow_job_template/1/details'],
}); await waitForElement(wrapper, 'ContentError', el => el.length === 1);
await act(async () => { });
wrapper = mountWithContexts( test('should call to get webhook key', async () => {
<Route const history = createMemoryHistory({
path="/templates/workflow_job_template/:id/details" initialEntries: ['/templates/workflow_job_template/1/foobar'],
component={() => ( });
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} /> await act(async () => {
)} wrapper = mountWithContexts(
/>, <WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />,
{ {
context: { context: {
router: { router: {
history, history,
route: {
location: history.location,
match: {
params: { id: 1 },
url: '/templates/workflow_job_template/1/foobar',
path: '/templates/workflow_job_template/1/foobar',
},
}, },
}, },
} },
); }
}); );
}); });
test('should not call for webhook key', async () => { expect(WorkflowJobTemplatesAPI.readWebhookKey).toHaveBeenCalled();
expect(WorkflowJobTemplatesAPI.readWebhookKey).not.toBeCalled(); });
test('should not call to get webhook key', async () => {
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValueOnce(
{
data: {
actions: {},
},
}
);
const history = createMemoryHistory({
initialEntries: ['/templates/workflow_job_template/1/foobar'],
}); });
await act(async () => {
wrapper = mountWithContexts(
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />,
{
context: {
router: {
history,
route: {
location: history.location,
match: {
params: { id: 1 },
url: '/templates/workflow_job_template/1/foobar',
path: '/templates/workflow_job_template/1/foobar',
},
},
},
},
}
);
});
expect(WorkflowJobTemplatesAPI.readWebhookKey).not.toHaveBeenCalled();
}); });
}); });

View File

@@ -0,0 +1,95 @@
{
"id": 15,
"type": "workflow_job_template",
"url": "/api/v2/workflow_job_templates/15/",
"related": {
"named_url": "/api/v2/workflow_job_templates/A workflow++/",
"created_by": "/api/v2/users/1/",
"modified_by": "/api/v2/users/1/",
"workflow_jobs": "/api/v2/workflow_job_templates/15/workflow_jobs/",
"schedules": "/api/v2/workflow_job_templates/15/schedules/",
"launch": "/api/v2/workflow_job_templates/15/launch/",
"webhook_key": "/api/v2/workflow_job_templates/15/webhook_key/",
"webhook_receiver": "/api/v2/workflow_job_templates/15/github/",
"workflow_nodes": "/api/v2/workflow_job_templates/15/workflow_nodes/",
"labels": "/api/v2/workflow_job_templates/15/labels/",
"activity_stream": "/api/v2/workflow_job_templates/15/activity_stream/",
"notification_templates_started": "/api/v2/workflow_job_templates/15/notification_templates_started/",
"notification_templates_success": "/api/v2/workflow_job_templates/15/notification_templates_success/",
"notification_templates_error": "/api/v2/workflow_job_templates/15/notification_templates_error/",
"notification_templates_approvals": "/api/v2/workflow_job_templates/15/notification_templates_approvals/",
"access_list": "/api/v2/workflow_job_templates/15/access_list/",
"object_roles": "/api/v2/workflow_job_templates/15/object_roles/",
"survey_spec": "/api/v2/workflow_job_templates/15/survey_spec/",
"copy": "/api/v2/workflow_job_templates/15/copy/"
},
"summary_fields": {
"created_by": {
"id": 1,
"username": "admin",
"first_name": "",
"last_name": ""
},
"modified_by": {
"id": 1,
"username": "admin",
"first_name": "",
"last_name": ""
},
"object_roles": {
"admin_role": {
"description": "Can manage all aspects of the workflow job template",
"name": "Admin",
"id": 68
},
"execute_role": {
"description": "May run the workflow job template",
"name": "Execute",
"id": 69
},
"read_role": {
"description": "May view settings for the workflow job template",
"name": "Read",
"id": 70
},
"approval_role": {
"description": "Can approve or deny a workflow approval node",
"name": "Approve",
"id": 71
}
},
"user_capabilities": {
"edit": true,
"delete": true,
"start": true,
"schedule": true,
"copy": true
},
"labels": {
"count": 0,
"results": []
},
"recent_jobs": []
},
"created": "2020-10-30T14:29:59.728159Z",
"modified": "2020-11-03T14:48:50.519450Z",
"name": "A workflow",
"description": "",
"last_job_run": null,
"last_job_failed": false,
"next_job_run": null,
"status": "never updated",
"extra_vars": "",
"organization": null,
"survey_enabled": false,
"allow_simultaneous": false,
"ask_variables_on_launch": false,
"inventory": null,
"limit": "",
"scm_branch": "",
"ask_inventory_on_launch": false,
"ask_scm_branch_on_launch": false,
"ask_limit_on_launch": false,
"webhook_service": "github",
"webhook_credential": null
}