diff --git a/awx/ui_next/src/components/DetailList/DeletedDetail.jsx b/awx/ui_next/src/components/DetailList/DeletedDetail.jsx
new file mode 100644
index 0000000000..e71fc183bd
--- /dev/null
+++ b/awx/ui_next/src/components/DetailList/DeletedDetail.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { node } from 'prop-types';
+import styled from 'styled-components';
+import _Detail from './Detail';
+
+const Detail = styled(_Detail)`
+ dd& {
+ color: red;
+ }
+`;
+
+function DeletedDetail({ i18n, label }) {
+ return ;
+}
+
+DeletedDetail.propTypes = {
+ label: node.isRequired,
+};
+
+export default withI18n()(DeletedDetail);
diff --git a/awx/ui_next/src/components/DetailList/index.js b/awx/ui_next/src/components/DetailList/index.js
index 4a5b77dbc0..8573f1a614 100644
--- a/awx/ui_next/src/components/DetailList/index.js
+++ b/awx/ui_next/src/components/DetailList/index.js
@@ -1,3 +1,4 @@
export { default as DetailList } from './DetailList';
export { default as Detail, DetailName, DetailValue } from './Detail';
+export { default as DeletedDetail } from './DeletedDetail';
export { default as UserDateDetail } from './UserDateDetail';
diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
index e2aa73ec6e..05ff230175 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
@@ -34,6 +34,21 @@ function formatTimeout(timeout) {
);
}
+function buildResourceLink(resource) {
+ const link = {
+ job_template: `/templates/job_template/${resource.id}/details`,
+ project: `/projects/${resource.id}/details`,
+ inventory_source: `/inventories/inventory/${resource.inventory}/sources/${resource.id}/details`,
+ workflow_job_template: `/templates/workflow_job_template/${resource.id}/details`,
+ };
+
+ return link[(resource?.type)] ? (
+ {resource.name}
+ ) : (
+ resource.name
+ );
+}
+
function hasPromptData(launchData) {
return (
launchData.ask_credential_on_launch ||
@@ -150,7 +165,7 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
return (
<>
-
+
- {details?.summary_fields?.organization && (
-
- {details?.summary_fields?.organization.name}
-
- }
- />
- )}
-
{details?.type === 'project' && (
)}
@@ -185,17 +187,20 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) {
{details?.type === 'workflow_job_template' && (
)}
-
-
-
+ {details?.created && (
+
+ )}
+ {details?.modified && (
+
+ )}
{hasPromptData(launchConfig) && hasOverrides && (
diff --git a/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx
index 6b385c5ef3..f3ec46c003 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx
@@ -4,7 +4,7 @@ import { t } from '@lingui/macro';
import { Link } from 'react-router-dom';
import { Chip, ChipGroup, List, ListItem } from '@patternfly/react-core';
-import { Detail } from '@components/DetailList';
+import { Detail, DeletedDetail } from '@components/DetailList';
import { VariablesDetail } from '@components/CodeMirrorInput';
import CredentialChip from '@components/CredentialChip';
@@ -57,6 +57,20 @@ function PromptInventorySourceDetail({ i18n, resource }) {
return (
<>
+ {summary_fields?.organization ? (
+
+ {summary_fields?.organization.name}
+
+ }
+ />
+ ) : (
+
+ )}
{summary_fields?.inventory && (
{
let wrapper;
@@ -21,18 +26,13 @@ describe('PromptInventorySourceDetail', () => {
});
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('Inventory', 'Demo Inventory');
- assertDetail('Source', 'scm');
- assertDetail('Project', 'Mock Project');
- assertDetail('Inventory File', 'foo');
- assertDetail('Custom Inventory Script', 'Mock Script');
- assertDetail('Verbosity', '2 (More Verbose)');
- assertDetail('Cache Timeout', '2 Seconds');
+ assertDetail(wrapper, 'Inventory', 'Demo Inventory');
+ assertDetail(wrapper, 'Source', 'scm');
+ assertDetail(wrapper, 'Project', 'Mock Project');
+ assertDetail(wrapper, 'Inventory File', 'foo');
+ assertDetail(wrapper, 'Custom Inventory Script', 'Mock Script');
+ assertDetail(wrapper, 'Verbosity', '2 (More Verbose)');
+ assertDetail(wrapper, 'Cache Timeout', '2 Seconds');
expect(
wrapper
.find('Detail[label="Regions"]')
@@ -74,4 +74,12 @@ describe('PromptInventorySourceDetail', () => {
])
).toEqual(true);
});
+
+ test('should render "Deleted" details', () => {
+ delete mockInvSource.summary_fields.organization;
+ wrapper = mountWithContexts(
+
+ );
+ assertDetail(wrapper, 'Organization', 'Deleted');
+ });
});
diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
index e8f979247b..d7502bb942 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx
@@ -4,7 +4,7 @@ import { t } from '@lingui/macro';
import { Link } from 'react-router-dom';
import { Chip, ChipGroup, List, ListItem } from '@patternfly/react-core';
-import { Detail } from '@components/DetailList';
+import { Detail, DeletedDetail } from '@components/DetailList';
import { VariablesDetail } from '@components/CodeMirrorInput';
import CredentialChip from '@components/CredentialChip';
import Sparkline from '@components/Sparkline';
@@ -13,6 +13,7 @@ import { toTitleCase } from '@util/strings';
function PromptJobTemplateDetail({ i18n, resource }) {
const {
allow_simultaneous,
+ ask_inventory_on_launch,
become_enabled,
diff_mode,
extra_vars,
@@ -84,7 +85,21 @@ function PromptJobTemplateDetail({ i18n, resource }) {
/>
)}
- {summary_fields?.inventory && (
+ {summary_fields?.organization ? (
+
+ {summary_fields?.organization.name}
+
+ }
+ />
+ ) : (
+
+ )}
+ {summary_fields?.inventory ? (
}
/>
+ ) : (
+ !ask_inventory_on_launch && (
+
+ )
)}
- {summary_fields?.project && (
+ {summary_fields?.project ? (
}
/>
+ ) : (
+
)}
diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx
index 30f92824ab..7a536f4b73 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx
@@ -18,6 +18,11 @@ const mockJT = {
],
};
+function assertDetail(wrapper, label, value) {
+ expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
+ expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
+}
+
describe('PromptJobTemplateDetail', () => {
let wrapper;
@@ -34,24 +39,19 @@ describe('PromptJobTemplateDetail', () => {
});
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');
- assertDetail('Webhook Service', 'Github');
- assertDetail('Webhook Key', 'PiM3n2');
+ assertDetail(wrapper, 'Job Type', 'Run');
+ assertDetail(wrapper, 'Inventory', 'Demo Inventory');
+ assertDetail(wrapper, 'Project', 'Mock Project');
+ assertDetail(wrapper, 'Source Control Branch', 'Foo branch');
+ assertDetail(wrapper, 'Playbook', 'ping.yml');
+ assertDetail(wrapper, 'Forks', '2');
+ assertDetail(wrapper, 'Limit', 'alpha:beta');
+ assertDetail(wrapper, 'Verbosity', '3 (Debug)');
+ assertDetail(wrapper, 'Show Changes', 'Off');
+ assertDetail(wrapper, 'Job Slicing', '1');
+ assertDetail(wrapper, 'Host Config Key', 'a1b2c3');
+ assertDetail(wrapper, 'Webhook Service', 'Github');
+ assertDetail(wrapper, 'Webhook Key', 'PiM3n2');
expect(wrapper.find('StatusIcon')).toHaveLength(2);
expect(wrapper.find('Detail[label="Webhook URL"] dd').text()).toEqual(
expect.stringContaining('/api/v2/job_templates/7/github/')
@@ -112,4 +112,16 @@ describe('PromptJobTemplateDetail', () => {
'---foo: bar'
);
});
+
+ test('should render "Deleted" details', () => {
+ delete mockJT.summary_fields.inventory;
+ delete mockJT.summary_fields.organization;
+ delete mockJT.summary_fields.project;
+
+ wrapper = mountWithContexts();
+
+ assertDetail(wrapper, 'Inventory', 'Deleted');
+ assertDetail(wrapper, 'Organization', 'Deleted');
+ assertDetail(wrapper, 'Project', 'Deleted');
+ });
});
diff --git a/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx
index 577a706d7d..b783631d14 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx
@@ -3,8 +3,9 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Config } from '@contexts/Config';
import { List, ListItem } from '@patternfly/react-core';
+import { Link } from 'react-router-dom';
-import { Detail } from '@components/DetailList';
+import { Detail, DeletedDetail } from '@components/DetailList';
import CredentialChip from '@components/CredentialChip';
import { toTitleCase } from '@util/strings';
@@ -49,6 +50,20 @@ function PromptProjectDetail({ i18n, resource }) {
return (
<>
+ {summary_fields?.organization ? (
+
+ {summary_fields?.organization.name}
+
+ }
+ />
+ ) : (
+
+ )}
{
let wrapper;
+ const config = {
+ project_base_dir: 'dir/foo/bar',
+ };
beforeAll(() => {
- const config = {
- project_base_dir: 'dir/foo/bar',
- };
wrapper = mountWithContexts(
,
{
@@ -27,23 +32,19 @@ describe('PromptProjectDetail', () => {
});
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('Source Control Type', 'Git');
+ assertDetail(wrapper, 'Source Control Type', 'Git');
assertDetail(
+ wrapper,
'Source Control URL',
'https://github.com/ansible/ansible-tower-samples'
);
- assertDetail('Source Control Branch', 'foo');
- assertDetail('Source Control Refspec', 'refs/');
- assertDetail('Cache Timeout', '3 Seconds');
- assertDetail('Ansible Environment', 'mock virtual env');
- assertDetail('Project Base Path', 'dir/foo/bar');
- assertDetail('Playbook Directory', '_6__demo_project');
- assertDetail('Source Control Credential', 'Scm: mock scm');
+ assertDetail(wrapper, 'Source Control Branch', 'foo');
+ assertDetail(wrapper, 'Source Control Refspec', 'refs/');
+ assertDetail(wrapper, 'Cache Timeout', '3 Seconds');
+ assertDetail(wrapper, 'Ansible Environment', 'mock virtual env');
+ assertDetail(wrapper, 'Project Base Path', 'dir/foo/bar');
+ assertDetail(wrapper, 'Playbook Directory', '_6__demo_project');
+ assertDetail(wrapper, 'Source Control Credential', 'Scm: mock scm');
expect(
wrapper
.find('Detail[label="Options"]')
@@ -55,4 +56,15 @@ describe('PromptProjectDetail', () => {
])
).toEqual(true);
});
+
+ test('should render "Deleted" details', () => {
+ delete mockProject.summary_fields.organization;
+ wrapper = mountWithContexts(
+ ,
+ {
+ context: { config },
+ }
+ );
+ assertDetail(wrapper, 'Organization', 'Deleted');
+ });
});
diff --git a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx
index dd38a766bd..c823b34184 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx
@@ -52,6 +52,18 @@ function PromptWFJobTemplateDetail({ i18n, resource }) {
label={i18n._(t`Activity`)}
/>
)}
+ {summary_fields?.organization && (
+
+ {summary_fields?.organization.name}
+
+ }
+ />
+ )}
{summary_fields?.inventory && (
);
- const renderMissingDataDetail = value => (
-
- );
-
const inventoryValue = (kind, id) => {
const inventorykind = kind === 'smart' ? 'smart_inventory' : 'inventory';
@@ -180,7 +174,7 @@ function JobTemplateDetail({ i18n, template }) {
}
/>
) : (
- renderMissingDataDetail(i18n._(t`Project`))
+
)}
{summary_fields.inventory ? (
) : (
- !ask_inventory_on_launch &&
- renderMissingDataDetail(i18n._(t`Inventory`))
+ !ask_inventory_on_launch && (
+
+ )
)}
{summary_fields.project ? (
) : (
- renderMissingDataDetail(i18n._(t`Project`))
+
)}