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(); });