mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 11:00:03 -03:30
Partition base resource into defaults and overrides
This commit is contained in:
parent
98e8a09ad3
commit
c6111fface
@ -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} <Trans>min</Trans> {seconds} <Trans>sec</Trans>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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} <Trans>min</Trans> {seconds} <Trans>sec</Trans>
|
||||
</>
|
||||
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 (
|
||||
<>
|
||||
<DetailList gutter="sm">
|
||||
<Detail label={i18n._(t`Name`)} value={resource.name} />
|
||||
<Detail label={i18n._(t`Description`)} value={resource.description} />
|
||||
<Detail label={i18n._(t`Name`)} value={details.name} />
|
||||
<Detail label={i18n._(t`Description`)} value={details.description} />
|
||||
<Detail
|
||||
label={i18n._(t`Type`)}
|
||||
value={resource.unified_job_type || resource.type}
|
||||
value={toTitleCase(details.unified_job_type || details.type)}
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Timeout`)}
|
||||
value={formatTimeout(resource?.timeout)}
|
||||
value={formatTimeout(details?.timeout)}
|
||||
/>
|
||||
{resource?.summary_fields?.organization && (
|
||||
{details?.summary_fields?.organization && (
|
||||
<Detail
|
||||
label={i18n._(t`Organization`)}
|
||||
value={
|
||||
<Link
|
||||
to={`/organizations/${resource?.summary_fields.organization.id}/details`}
|
||||
to={`/organizations/${details?.summary_fields.organization.id}/details`}
|
||||
>
|
||||
{resource?.summary_fields?.organization.name}
|
||||
{details?.summary_fields?.organization.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* TODO: Add JT, WFJT, Inventory Source Details */}
|
||||
{resource?.type === 'project' && (
|
||||
<PromptProjectDetail resource={resource} />
|
||||
{details?.type === 'project' && (
|
||||
<PromptProjectDetail resource={details} />
|
||||
)}
|
||||
{resource?.type === 'inventory_source' && (
|
||||
<PromptInventorySourceDetail resource={resource} />
|
||||
{details?.type === 'inventory_source' && (
|
||||
<PromptInventorySourceDetail resource={details} />
|
||||
)}
|
||||
{resource?.type === 'job_template' && (
|
||||
<PromptJobTemplateDetail resource={resource} />
|
||||
{details?.type === 'job_template' && (
|
||||
<PromptJobTemplateDetail resource={details} />
|
||||
)}
|
||||
{resource?.type === 'workflow_job_template' && (
|
||||
<PromptWFJobTemplateDetail resource={resource} />
|
||||
{details?.type === 'workflow_job_template' && (
|
||||
<PromptWFJobTemplateDetail resource={details} />
|
||||
)}
|
||||
|
||||
<UserDateDetail
|
||||
label={i18n._(t`Created`)}
|
||||
date={resource?.created}
|
||||
user={resource?.summary_fields?.created_by}
|
||||
date={details?.created}
|
||||
user={details?.summary_fields?.created_by}
|
||||
/>
|
||||
<UserDateDetail
|
||||
label={i18n._(t`Last Modified`)}
|
||||
date={resource?.modified}
|
||||
user={resource?.summary_fields?.modified_by}
|
||||
date={details?.modified}
|
||||
user={details?.summary_fields?.modified_by}
|
||||
/>
|
||||
</DetailList>
|
||||
|
||||
{hasPromptData(launchConfig) && (
|
||||
{hasPromptData(launchConfig) && hasOverrides && (
|
||||
<>
|
||||
<PromptHeader>{i18n._(t`Prompted Values`)}</PromptHeader>
|
||||
<DetailList>
|
||||
{launchConfig.ask_job_type_on_launch && (
|
||||
<Detail label={i18n._(t`Job Type`)} value={defaults?.job_type} />
|
||||
<DetailList aria-label="Prompt Overrides">
|
||||
{overrides?.job_type && (
|
||||
<Detail
|
||||
label={i18n._(t`Job Type`)}
|
||||
value={toTitleCase(overrides.job_type)}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_credential_on_launch && (
|
||||
{overrides?.credentials && (
|
||||
<Detail
|
||||
fullWidth
|
||||
label={i18n._(t`Credential`)}
|
||||
rows={4}
|
||||
value={
|
||||
<ChipGroup numChips={5}>
|
||||
{defaults?.credentials.map(cred => (
|
||||
{overrides.credentials.map(cred => (
|
||||
<Chip key={cred.id} isReadOnly>
|
||||
{cred.name}
|
||||
</Chip>
|
||||
@ -132,34 +238,34 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_inventory_on_launch && (
|
||||
{overrides?.inventory && (
|
||||
<Detail
|
||||
label={i18n._(t`Inventory`)}
|
||||
value={defaults?.inventory?.name}
|
||||
value={overrides.inventory?.name}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_scm_branch_on_launch && (
|
||||
{overrides?.scm_branch && (
|
||||
<Detail
|
||||
label={i18n._(t`Source Control Branch`)}
|
||||
value={defaults?.scm_branch}
|
||||
value={overrides.scm_branch}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_limit_on_launch && (
|
||||
<Detail label={i18n._(t`Limit`)} value={defaults?.limit} />
|
||||
{overrides?.limit && (
|
||||
<Detail label={i18n._(t`Limit`)} value={overrides.limit} />
|
||||
)}
|
||||
{launchConfig.ask_verbosity_on_launch && (
|
||||
{overrides?.verbosity && (
|
||||
<Detail
|
||||
label={i18n._(t`Verbosity`)}
|
||||
value={VERBOSITY[(defaults?.verbosity)]}
|
||||
value={VERBOSITY[overrides.verbosity]}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_tags_on_launch && (
|
||||
{overrides?.job_tags && (
|
||||
<Detail
|
||||
fullWidth
|
||||
label={i18n._(t`Job Tags`)}
|
||||
value={
|
||||
<ChipGroup numChips={5}>
|
||||
{defaults?.job_tags.split(',').map(jobTag => (
|
||||
{overrides.job_tags.split(',').map(jobTag => (
|
||||
<Chip key={jobTag} isReadOnly>
|
||||
{jobTag}
|
||||
</Chip>
|
||||
@ -168,13 +274,13 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_skip_tags_on_launch && (
|
||||
{overrides?.skip_tags && (
|
||||
<Detail
|
||||
fullWidth
|
||||
label={i18n._(t`Skip Tags`)}
|
||||
value={
|
||||
<ChipGroup numChips={5}>
|
||||
{defaults?.skip_tags.split(',').map(skipTag => (
|
||||
{overrides.skip_tags.split(',').map(skipTag => (
|
||||
<Chip key={skipTag} isReadOnly>
|
||||
{skipTag}
|
||||
</Chip>
|
||||
@ -183,19 +289,19 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_diff_mode_on_launch && (
|
||||
{overrides?.diff_mode && (
|
||||
<Detail
|
||||
label={i18n._(t`Diff Mode`)}
|
||||
label={i18n._(t`Show Changes`)}
|
||||
value={
|
||||
defaults?.diff_mode === true ? i18n._(t`On`) : i18n._(t`Off`)
|
||||
overrides.diff_mode === true ? i18n._(t`On`) : i18n._(t`Off`)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{launchConfig.ask_variables_on_launch && (
|
||||
{overrides?.extra_vars && (
|
||||
<VariablesDetail
|
||||
label={i18n._(t`Variables`)}
|
||||
rows={4}
|
||||
value={defaults?.extra_vars}
|
||||
value={overrides.extra_vars}
|
||||
/>
|
||||
)}
|
||||
</DetailList>
|
||||
|
||||
@ -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([
|
||||
<span>
|
||||
<strong>SSH:</strong>Credential 1
|
||||
</span>,
|
||||
<span>
|
||||
<strong>Awx:</strong>Credential 2
|
||||
</span>,
|
||||
])
|
||||
).toEqual(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('Detail[label="Job Tags"]')
|
||||
.containsAnyMatchingElements([<span>T_100</span>, <span>T_200</span>])
|
||||
).toEqual(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('Detail[label="Skip Tags"]')
|
||||
.containsAllMatchingElements([<span>S_100</span>, <span>S_200</span>])
|
||||
).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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<Detail label={i18n._(t`Job Type`)} value={job_type} />
|
||||
{summary_fields.recent_jobs?.length > 0 && (
|
||||
<Detail
|
||||
value={<Sparkline jobs={recentJobs} />}
|
||||
label={i18n._(t`Activity`)}
|
||||
/>
|
||||
)}
|
||||
<Detail label={i18n._(t`Job Type`)} value={toTitleCase(job_type)} />
|
||||
{summary_fields?.inventory && (
|
||||
<Detail
|
||||
label={i18n._(t`Inventory`)}
|
||||
value={
|
||||
<Link to={`/inventories/${summary_fields.inventory?.id}/details`}>
|
||||
<Link
|
||||
to={`/${inventoryKind}/${summary_fields.inventory?.id}/details`}
|
||||
>
|
||||
{summary_fields.inventory?.name}
|
||||
</Link>
|
||||
}
|
||||
@ -84,7 +104,7 @@ function PromptJobTemplateDetail({ i18n, resource }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Detail label={i18n._(t`SCM Branch`)} value={scm_branch} />
|
||||
<Detail label={i18n._(t`Source Control Branch`)} value={scm_branch} />
|
||||
<Detail label={i18n._(t`Playbook`)} value={playbook} />
|
||||
<Detail label={i18n._(t`Forks`)} value={forks || '0'} />
|
||||
<Detail label={i18n._(t`Limit`)} value={limit} />
|
||||
@ -103,6 +123,7 @@ function PromptJobTemplateDetail({ i18n, resource }) {
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{optionsList && <Detail label={i18n._(t`Options`)} value={optionsList} />}
|
||||
{summary_fields?.credentials?.length > 0 && (
|
||||
<Detail
|
||||
fullWidth
|
||||
@ -172,7 +193,6 @@ function PromptJobTemplateDetail({ i18n, resource }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{optionsList && <Detail label={i18n._(t`Options`)} value={optionsList} />}
|
||||
{extra_vars && (
|
||||
<VariablesDetail
|
||||
label={i18n._(t`Variables`)}
|
||||
|
||||
@ -38,10 +38,10 @@ describe('PromptJobTemplateDetail', () => {
|
||||
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');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user