diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
index 6ea976a0b5..cfb2745904 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
@@ -4,6 +4,7 @@ import { withI18n } from '@lingui/react';
import { t, Trans } from '@lingui/macro';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
+import { toTitleCase } from '@util/strings';
import { Chip, ChipGroup } from '@patternfly/react-core';
import { VariablesDetail } from '@components/CodeMirrorInput';
@@ -19,6 +20,19 @@ const PromptHeader = styled.h2`
margin: var(--pf-global--spacer--lg) 0;
`;
+function formatTimeout(timeout) {
+ if (typeof timeout === 'undefined' || timeout === null) {
+ return null;
+ }
+ const minutes = Math.floor(timeout / 60);
+ const seconds = timeout - Math.floor(timeout / 60) * 60;
+ return (
+
+ {minutes} min {seconds} sec
+
+ );
+}
+
function hasPromptData(launchData) {
return (
launchData.ask_credential_on_launch ||
@@ -34,21 +48,93 @@ function hasPromptData(launchData) {
);
}
-function formatTimeout(timeout) {
- if (typeof timeout === 'undefined' || timeout === null) {
- return null;
+function omitOverrides(resource, overrides) {
+ const clonedResource = {
+ ...resource,
+ summary_fields: { ...resource.summary_fields },
+ };
+ Object.keys(overrides).forEach(keyToOmit => {
+ delete clonedResource[keyToOmit];
+ delete clonedResource.summary_fields[keyToOmit];
+ });
+ return clonedResource;
+}
+
+// TODO: When prompting is hooked up, update function
+// to filter based on prompt overrides
+function partitionPromptDetails(resource, launchConfig) {
+ const { defaults = {} } = launchConfig;
+ const overrides = {};
+
+ if (launchConfig.ask_credential_on_launch) {
+ let isEqual;
+ const defaultCreds = defaults.credentials;
+ const currentCreds = resource?.summary_fields?.credentials;
+
+ if (defaultCreds?.length === currentCreds?.length) {
+ isEqual = currentCreds.every(cred => {
+ return defaultCreds.some(item => item.id === cred.id);
+ });
+ } else {
+ isEqual = false;
+ }
+
+ if (!isEqual) {
+ overrides.credentials = resource?.summary_fields?.credentials;
+ }
}
- const minutes = Math.floor(timeout / 60);
- const seconds = timeout - Math.floor(timeout / 60) * 60;
- return (
- <>
- {minutes} min {seconds} sec
- >
- );
+ if (launchConfig.ask_diff_mode_on_launch) {
+ if (defaults.diff_mode !== resource.diff_mode) {
+ overrides.diff_mode = resource.diff_mode;
+ }
+ }
+ if (launchConfig.ask_inventory_on_launch) {
+ if (defaults.inventory.id !== resource.inventory) {
+ overrides.inventory = resource?.summary_fields?.inventory;
+ }
+ }
+ if (launchConfig.ask_job_type_on_launch) {
+ if (defaults.job_type !== resource.job_type) {
+ overrides.job_type = resource.job_type;
+ }
+ }
+ if (launchConfig.ask_limit_on_launch) {
+ if (defaults.limit !== resource.limit) {
+ overrides.limit = resource.limit;
+ }
+ }
+ if (launchConfig.ask_scm_branch_on_launch) {
+ if (defaults.scm_branch !== resource.scm_branch) {
+ overrides.scm_branch = resource.scm_branch;
+ }
+ }
+ if (launchConfig.ask_skip_tags_on_launch) {
+ if (defaults.skip_tags !== resource.skip_tags) {
+ overrides.skip_tags = resource.skip_tags;
+ }
+ }
+ if (launchConfig.ask_tags_on_launch) {
+ if (defaults.job_tags !== resource.job_tags) {
+ overrides.job_tags = resource.job_tags;
+ }
+ }
+ if (launchConfig.ask_variables_on_launch) {
+ if (defaults.extra_vars !== resource.extra_vars) {
+ overrides.extra_vars = resource.extra_vars;
+ }
+ }
+ if (launchConfig.ask_verbosity_on_launch) {
+ if (defaults.verbosity !== resource.verbosity) {
+ overrides.verbosity = resource.verbosity;
+ }
+ }
+
+ const withoutOverrides = omitOverrides(resource, overrides);
+
+ return [withoutOverrides, overrides];
}
function PromptDetail({ i18n, resource, launchConfig = {} }) {
- const { defaults = {} } = launchConfig;
const VERBOSITY = {
0: i18n._(t`0 (Normal)`),
1: i18n._(t`1 (Verbose)`),
@@ -57,73 +143,79 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
4: i18n._(t`4 (Connection Debug)`),
};
+ const [details, overrides] = partitionPromptDetails(resource, launchConfig);
+ const hasOverrides = Object.keys(overrides).length > 0;
+
return (
<>
-
-
+
+
- {resource?.summary_fields?.organization && (
+ {details?.summary_fields?.organization && (
- {resource?.summary_fields?.organization.name}
+ {details?.summary_fields?.organization.name}
}
/>
)}
{/* TODO: Add JT, WFJT, Inventory Source Details */}
- {resource?.type === 'project' && (
-
+ {details?.type === 'project' && (
+
)}
- {resource?.type === 'inventory_source' && (
-
+ {details?.type === 'inventory_source' && (
+
)}
- {resource?.type === 'job_template' && (
-
+ {details?.type === 'job_template' && (
+
)}
- {resource?.type === 'workflow_job_template' && (
-
+ {details?.type === 'workflow_job_template' && (
+
)}
- {hasPromptData(launchConfig) && (
+ {hasPromptData(launchConfig) && hasOverrides && (
<>
{i18n._(t`Prompted Values`)}
-
- {launchConfig.ask_job_type_on_launch && (
-
+
+ {overrides?.job_type && (
+
)}
- {launchConfig.ask_credential_on_launch && (
+ {overrides?.credentials && (
- {defaults?.credentials.map(cred => (
+ {overrides.credentials.map(cred => (
{cred.name}
@@ -132,34 +224,34 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
}
/>
)}
- {launchConfig.ask_inventory_on_launch && (
+ {overrides?.inventory && (
)}
- {launchConfig.ask_scm_branch_on_launch && (
+ {overrides?.scm_branch && (
)}
- {launchConfig.ask_limit_on_launch && (
-
+ {overrides?.limit && (
+
)}
- {launchConfig.ask_verbosity_on_launch && (
+ {overrides?.verbosity && (
)}
- {launchConfig.ask_tags_on_launch && (
+ {overrides?.job_tags && (
- {defaults?.job_tags.split(',').map(jobTag => (
+ {overrides.job_tags.split(',').map(jobTag => (
{jobTag}
@@ -168,13 +260,13 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
}
/>
)}
- {launchConfig.ask_skip_tags_on_launch && (
+ {overrides?.skip_tags && (
- {defaults?.skip_tags.split(',').map(skipTag => (
+ {overrides.skip_tags.split(',').map(skipTag => (
{skipTag}
@@ -183,19 +275,19 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
}
/>
)}
- {launchConfig.ask_diff_mode_on_launch && (
+ {overrides?.diff_mode && (
)}
- {launchConfig.ask_variables_on_launch && (
+ {overrides?.extra_vars && (
)}
diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.test.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.test.jsx
index 265d1b227c..17ecfd4e30 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptDetail.test.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.test.jsx
@@ -1,16 +1,9 @@
import React from 'react';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import mockTemplate from './data.job_template.json';
import PromptDetail from './PromptDetail';
-const mockTemplate = {
- name: 'Mock Template',
- description: 'mock description',
- unified_job_type: 'job',
- created: '2019-08-08T19:24:05.344276Z',
- modified: '2019-08-08T19:24:18.162949Z',
-};
-
const mockPromptLaunch = {
ask_credential_on_launch: true,
ask_diff_mode_on_launch: true,
@@ -26,10 +19,10 @@ const mockPromptLaunch = {
extra_vars: '---foo: bar',
diff_mode: false,
limit: 3,
- job_tags: 'one,two,three',
- skip_tags: 'skip',
+ job_tags: 'T_100,T_200',
+ skip_tags: 'S_100,S_200',
job_type: 'run',
- verbosity: 1,
+ verbosity: 3,
inventory: {
name: 'Demo Inventory',
id: 1,
@@ -37,12 +30,16 @@ const mockPromptLaunch = {
credentials: [
{
id: 1,
- name: 'Demo Credential',
- credential_type: 1,
- passwords_needed: [],
+ kind: 'ssh',
+ name: 'Credential 1',
+ },
+ {
+ id: 2,
+ kind: 'awx',
+ name: 'Credential 2',
},
],
- scm_branch: '123',
+ scm_branch: 'Foo branch',
},
};
@@ -71,21 +68,40 @@ describe('PromptDetail', () => {
}
expect(wrapper.find('PromptDetail h2').text()).toBe('Prompted Values');
- assertDetail('Name', 'Mock Template');
- assertDetail('Description', 'mock description');
- assertDetail('Type', 'job');
- assertDetail('Job Type', 'run');
- assertDetail('Credential', 'Demo Credential');
+ assertDetail('Name', 'Mock JT');
+ assertDetail('Description', 'Mock JT Description');
+ assertDetail('Type', 'Job Template');
+ assertDetail('Job Type', 'Run');
assertDetail('Inventory', 'Demo Inventory');
- assertDetail('Source Control Branch', '123');
- assertDetail('Limit', '3');
- assertDetail('Verbosity', '1 (Verbose)');
- assertDetail('Job Tags', 'onetwothree');
- assertDetail('Skip Tags', 'skip');
- assertDetail('Diff Mode', 'Off');
+ assertDetail('Source Control Branch', 'Foo branch');
+ assertDetail('Limit', 'alpha:beta');
+ assertDetail('Verbosity', '3 (Debug)');
+ assertDetail('Show Changes', 'Off');
expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
'---foo: bar'
);
+ expect(
+ wrapper
+ .find('Detail[label="Credentials"]')
+ .containsAllMatchingElements([
+
+ SSH:Credential 1
+ ,
+
+ Awx:Credential 2
+ ,
+ ])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Job Tags"]')
+ .containsAnyMatchingElements([T_100, T_200])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Skip Tags"]')
+ .containsAllMatchingElements([S_100, S_200])
+ ).toEqual(true);
});
});
@@ -106,8 +122,11 @@ describe('PromptDetail', () => {
});
test('should not render promptable details', () => {
+ const overrideDetails = wrapper.find(
+ 'DetailList[aria-label="Prompt Overrides"]'
+ );
function assertNoDetail(label) {
- expect(wrapper.find(`Detail[label="${label}"]`).length).toBe(0);
+ expect(overrideDetails.find(`Detail[label="${label}"]`).length).toBe(0);
}
[
'Job Type',
@@ -120,8 +139,8 @@ describe('PromptDetail', () => {
'Skip Tags',
'Diff Mode',
].forEach(label => assertNoDetail(label));
- expect(wrapper.find('PromptDetail h2').length).toBe(0);
- expect(wrapper.find('VariablesDetail').length).toBe(0);
+ expect(overrideDetails.find('PromptDetail h2').length).toBe(0);
+ expect(overrideDetails.find('VariablesDetail').length).toBe(0);
});
});
});
diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
index ab68ca4b28..bb34806a19 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
@@ -1,8 +1,207 @@
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 PromptJobTemplateDetail() {
- return Coming soon :);
+import { Chip, ChipGroup, List, ListItem } from '@patternfly/react-core';
+import { Detail } from '@components/DetailList';
+import { VariablesDetail } from '@components/CodeMirrorInput';
+import CredentialChip from '@components/CredentialChip';
+import Sparkline from '@components/Sparkline';
+import { toTitleCase } from '@util/strings';
+
+function PromptJobTemplateDetail({ i18n, resource }) {
+ const {
+ allow_simultaneous,
+ become_enabled,
+ diff_mode,
+ extra_vars,
+ forks,
+ host_config_key,
+ instance_groups,
+ job_slice_count,
+ job_tags,
+ job_type,
+ limit,
+ playbook,
+ scm_branch,
+ skip_tags,
+ summary_fields,
+ url,
+ use_fact_cache,
+ verbosity,
+ } = resource;
+
+ const VERBOSITY = {
+ 0: i18n._(t`0 (Normal)`),
+ 1: i18n._(t`1 (Verbose)`),
+ 2: i18n._(t`2 (More Verbose)`),
+ 3: i18n._(t`3 (Debug)`),
+ 4: i18n._(t`4 (Connection Debug)`),
+ };
+
+ let optionsList = '';
+ if (
+ become_enabled ||
+ host_config_key ||
+ allow_simultaneous ||
+ use_fact_cache
+ ) {
+ optionsList = (
+
+ {become_enabled && (
+ {i18n._(t`Enable Privilege Escalation`)}
+ )}
+ {host_config_key && (
+ {i18n._(t`Allow Provisioning Callbacks`)}
+ )}
+ {allow_simultaneous && (
+ {i18n._(t`Enable Concurrent Jobs`)}
+ )}
+ {use_fact_cache && {i18n._(t`Use Fact Storage`)}}
+
+ );
+ }
+
+ 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}
+
+ }
+ />
+ )}
+ {summary_fields?.project && (
+
+ {summary_fields.project?.name}
+
+ }
+ />
+ )}
+
+
+
+
+
+
+
+ {host_config_key && (
+
+
+
+
+ )}
+ {optionsList && }
+ {summary_fields?.credentials?.length > 0 && (
+ (
+
+ ))}
+ />
+ )}
+ {summary_fields?.labels?.results?.length > 0 && (
+
+ {summary_fields.labels.results.map(label => (
+
+ {label.name}
+
+ ))}
+
+ }
+ />
+ )}
+ {instance_groups?.length > 0 && (
+
+ {instance_groups.map(ig => (
+
+ {ig.name}
+
+ ))}
+
+ }
+ />
+ )}
+ {job_tags?.length > 0 && (
+
+ {job_tags.split(',').map(jobTag => (
+
+ {jobTag}
+
+ ))}
+
+ }
+ />
+ )}
+ {skip_tags?.length > 0 && (
+
+ {skip_tags.split(',').map(skipTag => (
+
+ {skipTag}
+
+ ))}
+
+ }
+ />
+ )}
+ {extra_vars && (
+
+ )}
+ >
+ );
}
-export default PromptJobTemplateDetail;
+export default withI18n()(PromptJobTemplateDetail);
diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx
new file mode 100644
index 0000000000..b33af6c040
--- /dev/null
+++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import PromptJobTemplateDetail from './PromptJobTemplateDetail';
+import mockData from './data.job_template.json';
+
+const mockJT = {
+ ...mockData,
+ instance_groups: [
+ {
+ id: 1,
+ name: 'ig1',
+ },
+ {
+ id: 2,
+ name: 'ig2',
+ },
+ ],
+};
+
+describe('PromptJobTemplateDetail', () => {
+ let wrapper;
+
+ beforeAll(() => {
+ wrapper = mountWithContexts();
+ });
+
+ afterAll(() => {
+ wrapper.unmount();
+ });
+
+ test('should render successfully', () => {
+ expect(wrapper.find('PromptJobTemplateDetail')).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);
+ }
+
+ assertDetail('Job Type', 'Run');
+ assertDetail('Inventory', 'Demo Inventory');
+ assertDetail('Project', 'Mock Project');
+ assertDetail('Source Control Branch', 'Foo branch');
+ assertDetail('Playbook', 'ping.yml');
+ assertDetail('Forks', '2');
+ assertDetail('Limit', 'alpha:beta');
+ assertDetail('Verbosity', '3 (Debug)');
+ assertDetail('Show Changes', 'Off');
+ assertDetail('Job Slicing', '1');
+ assertDetail('Host Config Key', 'a1b2c3');
+ expect(
+ wrapper.find('Detail[label="Provisioning Callback URL"] dd').text()
+ ).toEqual(expect.stringContaining('/api/v2/job_templates/7/callback/'));
+ expect(
+ wrapper.find('Detail[label="Credentials"]').containsAllMatchingElements([
+
+ SSH:Credential 1
+ ,
+
+ Awx:Credential 2
+ ,
+ ])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Labels"]')
+ .containsAllMatchingElements([L_91o2, L_91o3])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Instance Groups"]')
+ .containsAllMatchingElements([ig1, ig2])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Job Tags"]')
+ .containsAllMatchingElements([T_100, T_200])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Skip Tags"]')
+ .containsAllMatchingElements([S_100, S_200])
+ ).toEqual(true);
+ expect(
+ wrapper
+ .find('Detail[label="Options"]')
+ .containsAllMatchingElements([
+ Enable Privilege Escalation,
+ Allow Provisioning Callbacks,
+ Enable Concurrent Jobs,
+ Use Fact Storage,
+ ])
+ ).toEqual(true);
+ expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
+ '---foo: bar'
+ );
+ });
+});
diff --git a/awx/ui_next/src/components/PromptDetail/data.job_template.json b/awx/ui_next/src/components/PromptDetail/data.job_template.json
new file mode 100644
index 0000000000..34dfc47154
--- /dev/null
+++ b/awx/ui_next/src/components/PromptDetail/data.job_template.json
@@ -0,0 +1,178 @@
+{
+ "id": 7,
+ "type": "job_template",
+ "url": "/api/v2/job_templates/7/",
+ "related": {
+ "named_url": "/api/v2/job_templates/MockJT/",
+ "created_by": "/api/v2/users/1/",
+ "modified_by": "/api/v2/users/1/",
+ "labels": "/api/v2/job_templates/7/labels/",
+ "inventory": "/api/v2/inventories/1/",
+ "project": "/api/v2/projects/6/",
+ "extra_credentials": "/api/v2/job_templates/7/extra_credentials/",
+ "credentials": "/api/v2/job_templates/7/credentials/",
+ "last_job": "/api/v2/jobs/12/",
+ "jobs": "/api/v2/job_templates/7/jobs/",
+ "schedules": "/api/v2/job_templates/7/schedules/",
+ "activity_stream": "/api/v2/job_templates/7/activity_stream/",
+ "launch": "/api/v2/job_templates/7/launch/",
+ "notification_templates_started": "/api/v2/job_templates/7/notification_templates_started/",
+ "notification_templates_success": "/api/v2/job_templates/7/notification_templates_success/",
+ "notification_templates_error": "/api/v2/job_templates/7/notification_templates_error/",
+ "access_list": "/api/v2/job_templates/7/access_list/",
+ "survey_spec": "/api/v2/job_templates/7/survey_spec/",
+ "object_roles": "/api/v2/job_templates/7/object_roles/",
+ "instance_groups": "/api/v2/job_templates/7/instance_groups/",
+ "slice_workflow_jobs": "/api/v2/job_templates/7/slice_workflow_jobs/",
+ "copy": "/api/v2/job_templates/7/copy/"
+ },
+ "summary_fields": {
+ "inventory": {
+ "id": 1,
+ "name": "Demo Inventory",
+ "description": "",
+ "has_active_failures": false,
+ "total_hosts": 1,
+ "hosts_with_active_failures": 0,
+ "total_groups": 0,
+ "groups_with_active_failures": 0,
+ "has_inventory_sources": false,
+ "total_inventory_sources": 0,
+ "inventory_sources_with_failures": 0,
+ "organization_id": 1,
+ "kind": ""
+ },
+ "project": {
+ "id": 6,
+ "name": "Mock Project",
+ "description": "",
+ "status": "successful",
+ "scm_type": "git"
+ },
+ "last_job": {
+ "id": 12,
+ "name": "Mock JT",
+ "description": "",
+ "finished": "2019-10-01T14:34:35.142483Z",
+ "status": "successful",
+ "failed": false
+ },
+ "last_update": {
+ "id": 12,
+ "name": "Mock JT",
+ "description": "",
+ "status": "successful",
+ "failed": false
+ },
+ "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 job template",
+ "name": "Admin",
+ "id": 24
+ },
+ "execute_role": {
+ "description": "May run the job template",
+ "name": "Execute",
+ "id": 25
+ },
+ "read_role": {
+ "description": "May view settings for the job template",
+ "name": "Read",
+ "id": 26
+ }
+ },
+ "user_capabilities": {
+ "edit": true,
+ "delete": true,
+ "start": true,
+ "schedule": true,
+ "copy": true
+ },
+ "labels": {
+ "count": 1,
+ "results": [
+ {
+ "id": 91,
+ "name": "L_91o2"
+ },
+ {
+ "id": 92,
+ "name": "L_91o3"
+ }
+ ]
+ },
+ "survey": {
+ "title": "",
+ "description": ""
+ },
+ "recent_jobs": [
+ {
+ "id": 12,
+ "status": "successful",
+ "finished": "2019-10-01T14:34:35.142483Z",
+ "type": "job"
+ }
+ ],
+ "extra_credentials": [],
+ "credentials": [
+ {
+ "id": 1, "kind": "ssh" , "name": "Credential 1"
+ },
+ {
+ "id": 2, "kind": "awx" , "name": "Credential 2"
+ }
+ ]
+ },
+ "created": "2019-09-30T16:18:34.564820Z",
+ "modified": "2019-10-01T14:47:31.818431Z",
+ "name": "Mock JT",
+ "description": "Mock JT Description",
+ "job_type": "run",
+ "inventory": 1,
+ "project": 6,
+ "playbook": "ping.yml",
+ "scm_branch": "Foo branch",
+ "forks": 2,
+ "limit": "alpha:beta",
+ "verbosity": 3,
+ "extra_vars": "---foo: bar",
+ "job_tags": "T_100,T_200",
+ "force_handlers": false,
+ "skip_tags": "S_100,S_200",
+ "start_at_task": "",
+ "timeout": 0,
+ "use_fact_cache": true,
+ "last_job_run": "2019-10-01T14:34:35.142483Z",
+ "last_job_failed": false,
+ "next_job_run": null,
+ "status": "successful",
+ "host_config_key": "a1b2c3",
+ "ask_scm_branch_on_launch": false,
+ "ask_diff_mode_on_launch": false,
+ "ask_variables_on_launch": false,
+ "ask_limit_on_launch": false,
+ "ask_tags_on_launch": false,
+ "ask_skip_tags_on_launch": false,
+ "ask_job_type_on_launch": false,
+ "ask_verbosity_on_launch": false,
+ "ask_inventory_on_launch": false,
+ "ask_credential_on_launch": false,
+ "survey_enabled": true,
+ "become_enabled": true,
+ "diff_mode": false,
+ "allow_simultaneous": true,
+ "custom_virtualenv": null,
+ "job_slice_count": 1
+}
\ No newline at end of file
diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
index baa6407bc6..cc8ff2e861 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
@@ -294,7 +294,7 @@ function JobTemplateDetail({ i18n, template }) {
{job_tags && job_tags.length > 0 && (
{job_tags.split(',').map(jobTag => (
@@ -309,7 +309,7 @@ function JobTemplateDetail({ i18n, template }) {
{skip_tags && skip_tags.length > 0 && (
{skip_tags.split(',').map(skipTag => (
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 7ca5f4cf63..e550de8ae4 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
@@ -71,7 +71,14 @@ function NodeViewModal({ i18n }) {
request: fetchNodeDetail,
} = useRequest(
useCallback(async () => {
- const { data } = await nodeAPI?.readDetail(unifiedJobTemplate.id);
+ let { data } = await nodeAPI?.readDetail(unifiedJobTemplate.id);
+ if (data?.type === 'job_template') {
+ const {
+ data: { results = [] },
+ } = await JobTemplatesAPI.readInstanceGroups(data.id);
+ data = Object.assign(data, { instance_groups: results });
+ }
+
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 d4f22b14de..6edcfbbd66 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
@@ -13,6 +13,13 @@ jest.mock('@api/models/WorkflowJobTemplates');
WorkflowJobTemplatesAPI.readLaunch.mockResolvedValue({});
WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({});
JobTemplatesAPI.readLaunch.mockResolvedValue({});
+JobTemplatesAPI.readInstanceGroups.mockResolvedValue({});
+JobTemplatesAPI.readDetail.mockResolvedValue({
+ data: {
+ id: 1,
+ type: 'job_template',
+ },
+});
const dispatch = jest.fn();
@@ -64,6 +71,8 @@ describe('NodeViewModal', () => {
test('should fetch workflow template launch data', () => {
expect(JobTemplatesAPI.readLaunch).not.toHaveBeenCalled();
+ expect(JobTemplatesAPI.readDetail).not.toHaveBeenCalled();
+ expect(JobTemplatesAPI.readInstanceGroups).not.toHaveBeenCalled();
expect(WorkflowJobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
});
@@ -95,7 +104,7 @@ describe('NodeViewModal', () => {
id: 1,
name: 'Mock Node',
description: '',
- type: 'job_template',
+ unified_job_type: 'job',
created: '2019-08-08T19:24:05.344276Z',
modified: '2019-08-08T19:24:18.162949Z',
},
@@ -104,6 +113,7 @@ describe('NodeViewModal', () => {
test('should fetch job template launch data', async () => {
let wrapper;
+
await act(async () => {
wrapper = mountWithContexts(
@@ -116,6 +126,8 @@ describe('NodeViewModal', () => {
waitForLoaded(wrapper);
expect(WorkflowJobTemplatesAPI.readLaunch).not.toHaveBeenCalled();
expect(JobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
+ expect(JobTemplatesAPI.readDetail).toHaveBeenCalledWith(1);
+ expect(JobTemplatesAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
wrapper.unmount();
jest.clearAllMocks();
});
@@ -167,6 +179,7 @@ describe('NodeViewModal', () => {
waitForLoaded(wrapper);
expect(WorkflowJobTemplatesAPI.readLaunch).not.toHaveBeenCalled();
expect(JobTemplatesAPI.readLaunch).not.toHaveBeenCalled();
+ expect(JobTemplatesAPI.readInstanceGroups).not.toHaveBeenCalled();
wrapper.unmount();
jest.clearAllMocks();
});