diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
index 6ea976a0b5..2aafad7259 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,107 @@ function hasPromptData(launchData) {
);
}
-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 removeOverrides(resource, overrides) {
+ const filteredResource = Object.keys(overrides).reduce(
+ (acc, value) => {
+ let root;
+ let nested;
+
+ ({
+ [value]: acc[value],
+ summary_fields: { [value]: acc[value], ...nested },
+ ...root
+ } = acc);
+
+ const filtered = {
+ ...root,
+ summary_fields: {
+ ...nested,
+ },
+ };
+
+ return filtered;
+ },
+ { ...resource }
);
+ return filteredResource;
+}
+
+// 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;
+ }
+ }
+ 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 = removeOverrides(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 +157,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 +238,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 +274,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 +289,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 7c0d555485..bb34806a19 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
@@ -7,6 +7,8 @@ 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 {
@@ -61,14 +63,32 @@ function PromptJobTemplateDetail({ i18n, resource }) {
);
}
+ 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}
}
@@ -84,7 +104,7 @@ function PromptJobTemplateDetail({ i18n, resource }) {
}
/>
)}
-
+
@@ -103,6 +123,7 @@ function PromptJobTemplateDetail({ i18n, resource }) {
/>
)}
+ {optionsList && }
{summary_fields?.credentials?.length > 0 && (
)}
- {optionsList && }
{extra_vars && (
{
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
}
- assertDetail('Job Type', 'run');
+ assertDetail('Job Type', 'Run');
assertDetail('Inventory', 'Demo Inventory');
assertDetail('Project', 'Mock Project');
- assertDetail('SCM Branch', 'Foo branch');
+ assertDetail('Source Control Branch', 'Foo branch');
assertDetail('Playbook', 'ping.yml');
assertDetail('Forks', '2');
assertDetail('Limit', 'alpha:beta');