diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js
index 5447817eaa..522240c733 100644
--- a/awx/ui_next/src/api/models/JobTemplates.js
+++ b/awx/ui_next/src/api/models/JobTemplates.js
@@ -16,6 +16,7 @@ class JobTemplates extends SchedulesMixin(
this.disassociateLabel = this.disassociateLabel.bind(this);
this.readCredentials = this.readCredentials.bind(this);
this.readAccessList = this.readAccessList.bind(this);
+ this.readWebhookKey = this.readWebhookKey.bind(this);
}
launch(id, data) {
@@ -82,6 +83,10 @@ class JobTemplates extends SchedulesMixin(
destroySurvey(id) {
return this.http.delete(`${this.baseUrl}${id}/survey_spec/`);
}
+
+ readWebhookKey(id) {
+ return this.http.get(`${this.baseUrl}${id}/webhook_key/`);
+ }
}
export default JobTemplates;
diff --git a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx
index cceaa02cd8..dd38a766bd 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx
@@ -1,8 +1,120 @@
import React from 'react';
-import { CardBody } from '@components/Card';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { Link } from 'react-router-dom';
-function PromptWFJobTemplateDetail() {
- return Coming soon :);
+import { Chip, ChipGroup, List, ListItem } from '@patternfly/react-core';
+import CredentialChip from '@components/CredentialChip';
+import { Detail } from '@components/DetailList';
+import { VariablesDetail } from '@components/CodeMirrorInput';
+import Sparkline from '@components/Sparkline';
+import { toTitleCase } from '@util/strings';
+
+function PromptWFJobTemplateDetail({ i18n, resource }) {
+ const {
+ allow_simultaneous,
+ extra_vars,
+ limit,
+ related,
+ scm_branch,
+ summary_fields,
+ webhook_key,
+ webhook_service,
+ } = resource;
+
+ let optionsList = '';
+ if (allow_simultaneous || webhook_service) {
+ optionsList = (
+
+ {allow_simultaneous && (
+ {i18n._(t`Enable Concurrent Jobs`)}
+ )}
+ {webhook_service && {i18n._(t`Enable Webhooks`)}}
+
+ );
+ }
+
+ const inventoryKind =
+ summary_fields?.inventory?.kind === 'smart'
+ ? 'smart_inventory'
+ : 'inventory';
+
+ const recentJobs = summary_fields.recent_jobs.map(job => ({
+ ...job,
+ type: 'job',
+ }));
+
+ return (
+ <>
+ {summary_fields.recent_jobs?.length > 0 && (
+ }
+ label={i18n._(t`Activity`)}
+ />
+ )}
+ {summary_fields?.inventory && (
+
+ {summary_fields.inventory?.name}
+
+ }
+ />
+ )}
+
+
+
+
+ {related.webhook_receiver && (
+
+ )}
+ {optionsList && }
+ {summary_fields?.webhook_credential && (
+
+ }
+ />
+ )}
+ {summary_fields?.labels?.results?.length > 0 && (
+
+ {summary_fields.labels.results.map(label => (
+
+ {label.name}
+
+ ))}
+
+ }
+ />
+ )}
+ {extra_vars && (
+
+ )}
+ >
+ );
}
-export default PromptWFJobTemplateDetail;
+export default withI18n()(PromptWFJobTemplateDetail);
diff --git a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx
new file mode 100644
index 0000000000..952745fba9
--- /dev/null
+++ b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import PromptWFJobTemplateDetail from './PromptWFJobTemplateDetail';
+import mockData from './data.workflow_template.json';
+
+const mockWF = {
+ ...mockData,
+ webhook_key: 'Pim3mRXT0',
+};
+
+describe('PromptWFJobTemplateDetail', () => {
+ let wrapper;
+
+ beforeAll(() => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+
+ afterAll(() => {
+ wrapper.unmount();
+ });
+
+ test('should render successfully', () => {
+ expect(wrapper.find('PromptWFJobTemplateDetail')).toHaveLength(1);
+ });
+
+ test('should render expected details', () => {
+ function assertDetail(label, value) {
+ expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
+ expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
+ }
+
+ expect(wrapper.find('StatusIcon')).toHaveLength(1);
+ assertDetail('Inventory', 'Mock Smart Inv');
+ assertDetail('Source Control Branch', '/bar/');
+ assertDetail('Limit', 'hosts1,hosts2');
+ assertDetail('Webhook Service', 'Github');
+ assertDetail('Webhook Key', 'Pim3mRXT0');
+ expect(wrapper.find('Detail[label="Webhook URL"] dd').text()).toEqual(
+ expect.stringContaining('/api/v2/workflow_job_templates/47/github/')
+ );
+ expect(
+ wrapper
+ .find('Detail[label="Options"]')
+ .containsAllMatchingElements([
+ Enable Concurrent Jobs,
+ Enable Webhooks,
+ ])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Webhook Credential"]')
+ .containsAllMatchingElements([
+
+ Github Token:github
+ ,
+ ])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Labels"]')
+ .containsAllMatchingElements([L_10o0, L_20o0])
+ ).toEqual(true);
+ expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
+ '---\nmock: data'
+ );
+ });
+});
diff --git a/awx/ui_next/src/components/PromptDetail/data.workflow_template.json b/awx/ui_next/src/components/PromptDetail/data.workflow_template.json
new file mode 100644
index 0000000000..0f10fdc53e
--- /dev/null
+++ b/awx/ui_next/src/components/PromptDetail/data.workflow_template.json
@@ -0,0 +1,156 @@
+{
+ "id": 47,
+ "type": "workflow_job_template",
+ "url": "/api/v2/workflow_job_templates/47/",
+ "related": {
+ "created_by": "/api/v2/users/8/",
+ "modified_by": "/api/v2/users/1/",
+ "last_job": "/api/v2/workflow_jobs/226/",
+ "workflow_jobs": "/api/v2/workflow_job_templates/47/workflow_jobs/",
+ "schedules": "/api/v2/workflow_job_templates/47/schedules/",
+ "launch": "/api/v2/workflow_job_templates/47/launch/",
+ "webhook_key": "/api/v2/workflow_job_templates/47/webhook_key/",
+ "webhook_receiver": "/api/v2/workflow_job_templates/47/github/",
+ "workflow_nodes": "/api/v2/workflow_job_templates/47/workflow_nodes/",
+ "labels": "/api/v2/workflow_job_templates/47/labels/",
+ "activity_stream": "/api/v2/workflow_job_templates/47/activity_stream/",
+ "notification_templates_started": "/api/v2/workflow_job_templates/47/notification_templates_started/",
+ "notification_templates_success": "/api/v2/workflow_job_templates/47/notification_templates_success/",
+ "notification_templates_error": "/api/v2/workflow_job_templates/47/notification_templates_error/",
+ "notification_templates_approvals": "/api/v2/workflow_job_templates/47/notification_templates_approvals/",
+ "access_list": "/api/v2/workflow_job_templates/47/access_list/",
+ "object_roles": "/api/v2/workflow_job_templates/47/object_roles/",
+ "survey_spec": "/api/v2/workflow_job_templates/47/survey_spec/",
+ "copy": "/api/v2/workflow_job_templates/47/copy/",
+ "organization": "/api/v2/organizations/3/",
+ "webhook_credential": "/api/v2/credentials/8/"
+ },
+ "summary_fields": {
+ "organization": {
+ "id": 3,
+ "name": "Mock Org",
+ "description": ""
+ },
+ "inventory": {
+ "id": 7,
+ "name": "Mock Smart Inv",
+ "description": "",
+ "has_active_failures": false,
+ "total_hosts": 1,
+ "hosts_with_active_failures": 0,
+ "total_groups": 0,
+ "has_inventory_sources": false,
+ "total_inventory_sources": 0,
+ "inventory_sources_with_failures": 0,
+ "organization_id": 1,
+ "kind": "smart"
+ },
+ "last_job": {
+ "id": 226,
+ "name": "abc",
+ "description": "From Tower bulk-data script",
+ "finished": "2020-04-08T21:30:44.282245Z",
+ "status": "failed",
+ "failed": true
+ },
+ "last_update": {
+ "id": 226,
+ "name": "abc",
+ "description": "From Tower bulk-data script",
+ "status": "failed",
+ "failed": true
+ },
+ "webhook_credential": {
+ "id": 8,
+ "name": "github",
+ "description": "",
+ "kind": "github_token",
+ "cloud": false,
+ "credential_type_id": 12
+ },
+ "created_by": {
+ "id": 8,
+ "username": "user-2",
+ "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": 260
+ },
+ "execute_role": {
+ "description": "May run the workflow job template",
+ "name": "Execute",
+ "id": 261
+ },
+ "read_role": {
+ "description": "May view settings for the workflow job template",
+ "name": "Read",
+ "id": 262
+ },
+ "approval_role": {
+ "description": "Can approve or deny a workflow approval node",
+ "name": "Approve",
+ "id": 263
+ }
+ },
+ "user_capabilities": {
+ "edit": true,
+ "delete": true,
+ "start": true,
+ "schedule": true,
+ "copy": true
+ },
+ "labels": {
+ "count": 2,
+ "results": [
+ {
+ "id": 104,
+ "name": "L_10o0"
+ },
+ {
+ "id": 105,
+ "name": "L_20o0"
+ }
+ ]
+ },
+ "recent_jobs": [
+ {
+ "id": 226,
+ "status": "failed",
+ "finished": "2020-04-08T21:30:44.282245Z",
+ "canceled_on": null,
+ "type": "workflow_job"
+ }
+ ]
+ },
+ "created": "2020-04-07T16:38:02.856877Z",
+ "modified": "2020-04-13T20:53:53.761355Z",
+ "name": "Mock Workflow",
+ "description": "Mock WF Description",
+ "last_job_run": "2020-04-08T21:30:44.282245Z",
+ "last_job_failed": true,
+ "next_job_run": null,
+ "status": "failed",
+ "extra_vars": "---\nmock: data",
+ "organization": 3,
+ "survey_enabled": false,
+ "allow_simultaneous": true,
+ "ask_variables_on_launch": false,
+ "inventory": 7,
+ "limit": "hosts1,hosts2",
+ "scm_branch": "/bar/",
+ "ask_inventory_on_launch": true,
+ "ask_scm_branch_on_launch": true,
+ "ask_limit_on_launch": true,
+ "webhook_service": "github",
+ "webhook_credential": 8
+}
\ No newline at end of file
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx
index e550de8ae4..c985054e2e 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx
@@ -72,6 +72,7 @@ function NodeViewModal({ i18n }) {
} = useRequest(
useCallback(async () => {
let { data } = await nodeAPI?.readDetail(unifiedJobTemplate.id);
+
if (data?.type === 'job_template') {
const {
data: { results = [] },
@@ -79,6 +80,13 @@ function NodeViewModal({ i18n }) {
data = Object.assign(data, { instance_groups: results });
}
+ if (data?.related?.webhook_receiver) {
+ const {
+ data: { webhook_key },
+ } = await nodeAPI?.readWebhookKey(data.id);
+ data = Object.assign(data, { webhook_key });
+ }
+
return data;
}, [nodeAPI, unifiedJobTemplate.id]),
null
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.test.jsx
index 6edcfbbd66..7d049d70e1 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.test.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.test.jsx
@@ -11,9 +11,23 @@ import NodeViewModal from './NodeViewModal';
jest.mock('@api/models/JobTemplates');
jest.mock('@api/models/WorkflowJobTemplates');
WorkflowJobTemplatesAPI.readLaunch.mockResolvedValue({});
-WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({});
+WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({
+ data: {
+ id: 1,
+ type: 'workflow_job_template',
+ related: {
+ webhook_receiver: '/api/v2/job_templates/7/gitlab/',
+ },
+ },
+});
+WorkflowJobTemplatesAPI.readWebhookKey.mockResolvedValue({
+ data: {
+ webhook_key: 'Pim3mRXT0',
+ },
+});
JobTemplatesAPI.readLaunch.mockResolvedValue({});
JobTemplatesAPI.readInstanceGroups.mockResolvedValue({});
+JobTemplatesAPI.readWebhookKey.mockResolvedValue({});
JobTemplatesAPI.readDetail.mockResolvedValue({
data: {
id: 1,
@@ -74,6 +88,7 @@ describe('NodeViewModal', () => {
expect(JobTemplatesAPI.readDetail).not.toHaveBeenCalled();
expect(JobTemplatesAPI.readInstanceGroups).not.toHaveBeenCalled();
expect(WorkflowJobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
+ expect(WorkflowJobTemplatesAPI.readWebhookKey).toHaveBeenCalledWith(1);
});
test('Close button dispatches as expected', () => {
@@ -125,6 +140,7 @@ describe('NodeViewModal', () => {
});
waitForLoaded(wrapper);
expect(WorkflowJobTemplatesAPI.readLaunch).not.toHaveBeenCalled();
+ expect(JobTemplatesAPI.readWebhookKey).not.toHaveBeenCalledWith();
expect(JobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
expect(JobTemplatesAPI.readDetail).toHaveBeenCalledWith(1);
expect(JobTemplatesAPI.readInstanceGroups).toHaveBeenCalledTimes(1);