From 4551859248d5a99c686c99690a4242877686a51f Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 14 Apr 2020 15:04:21 -0400 Subject: [PATCH 1/3] Add WF details to workflow node view --- awx/ui_next/src/api/models/JobTemplates.js | 5 + .../PromptWFJobTemplateDetail.jsx | 120 +++++++++++++- .../PromptWFJobTemplateDetail.test.jsx | 69 ++++++++ .../PromptDetail/data.workflow_template.json | 156 ++++++++++++++++++ .../Modals/NodeModals/NodeViewModal.jsx | 8 + .../Modals/NodeModals/NodeViewModal.test.jsx | 18 +- 6 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx create mode 100644 awx/ui_next/src/components/PromptDetail/data.workflow_template.json diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js index 5447817eaa..522240c733 100644 --- a/awx/ui_next/src/api/models/JobTemplates.js +++ b/awx/ui_next/src/api/models/JobTemplates.js @@ -16,6 +16,7 @@ class JobTemplates extends SchedulesMixin( this.disassociateLabel = this.disassociateLabel.bind(this); this.readCredentials = this.readCredentials.bind(this); this.readAccessList = this.readAccessList.bind(this); + this.readWebhookKey = this.readWebhookKey.bind(this); } launch(id, data) { @@ -82,6 +83,10 @@ class JobTemplates extends SchedulesMixin( destroySurvey(id) { return this.http.delete(`${this.baseUrl}${id}/survey_spec/`); } + + readWebhookKey(id) { + return this.http.get(`${this.baseUrl}${id}/webhook_key/`); + } } export default JobTemplates; diff --git a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx index cceaa02cd8..dd38a766bd 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx @@ -1,8 +1,120 @@ 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 PromptWFJobTemplateDetail() { - return Coming soon :); +import { Chip, ChipGroup, List, ListItem } from '@patternfly/react-core'; +import CredentialChip from '@components/CredentialChip'; +import { Detail } from '@components/DetailList'; +import { VariablesDetail } from '@components/CodeMirrorInput'; +import Sparkline from '@components/Sparkline'; +import { toTitleCase } from '@util/strings'; + +function PromptWFJobTemplateDetail({ i18n, resource }) { + const { + allow_simultaneous, + extra_vars, + limit, + related, + scm_branch, + summary_fields, + webhook_key, + webhook_service, + } = resource; + + let optionsList = ''; + if (allow_simultaneous || webhook_service) { + optionsList = ( + + {allow_simultaneous && ( + {i18n._(t`Enable Concurrent Jobs`)} + )} + {webhook_service && {i18n._(t`Enable Webhooks`)}} + + ); + } + + 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} + + } + /> + )} + + + + + {related.webhook_receiver && ( + + )} + {optionsList && } + {summary_fields?.webhook_credential && ( + + } + /> + )} + {summary_fields?.labels?.results?.length > 0 && ( + + {summary_fields.labels.results.map(label => ( + + {label.name} + + ))} + + } + /> + )} + {extra_vars && ( + + )} + + ); } -export default PromptWFJobTemplateDetail; +export default withI18n()(PromptWFJobTemplateDetail); diff --git a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx new file mode 100644 index 0000000000..952745fba9 --- /dev/null +++ b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.test.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { mountWithContexts } from '@testUtils/enzymeHelpers'; +import PromptWFJobTemplateDetail from './PromptWFJobTemplateDetail'; +import mockData from './data.workflow_template.json'; + +const mockWF = { + ...mockData, + webhook_key: 'Pim3mRXT0', +}; + +describe('PromptWFJobTemplateDetail', () => { + let wrapper; + + beforeAll(() => { + wrapper = mountWithContexts( + + ); + }); + + afterAll(() => { + wrapper.unmount(); + }); + + test('should render successfully', () => { + expect(wrapper.find('PromptWFJobTemplateDetail')).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); + } + + expect(wrapper.find('StatusIcon')).toHaveLength(1); + assertDetail('Inventory', 'Mock Smart Inv'); + assertDetail('Source Control Branch', '/bar/'); + assertDetail('Limit', 'hosts1,hosts2'); + assertDetail('Webhook Service', 'Github'); + assertDetail('Webhook Key', 'Pim3mRXT0'); + expect(wrapper.find('Detail[label="Webhook URL"] dd').text()).toEqual( + expect.stringContaining('/api/v2/workflow_job_templates/47/github/') + ); + expect( + wrapper + .find('Detail[label="Options"]') + .containsAllMatchingElements([ +
  • Enable Concurrent Jobs
  • , +
  • Enable Webhooks
  • , + ]) + ).toEqual(true); + expect( + wrapper + .find('Detail[label="Webhook Credential"]') + .containsAllMatchingElements([ + + Github Token:github + , + ]) + ).toEqual(true); + expect( + wrapper + .find('Detail[label="Labels"]') + .containsAllMatchingElements([L_10o0, L_20o0]) + ).toEqual(true); + expect(wrapper.find('VariablesDetail').prop('value')).toEqual( + '---\nmock: data' + ); + }); +}); diff --git a/awx/ui_next/src/components/PromptDetail/data.workflow_template.json b/awx/ui_next/src/components/PromptDetail/data.workflow_template.json new file mode 100644 index 0000000000..0f10fdc53e --- /dev/null +++ b/awx/ui_next/src/components/PromptDetail/data.workflow_template.json @@ -0,0 +1,156 @@ +{ + "id": 47, + "type": "workflow_job_template", + "url": "/api/v2/workflow_job_templates/47/", + "related": { + "created_by": "/api/v2/users/8/", + "modified_by": "/api/v2/users/1/", + "last_job": "/api/v2/workflow_jobs/226/", + "workflow_jobs": "/api/v2/workflow_job_templates/47/workflow_jobs/", + "schedules": "/api/v2/workflow_job_templates/47/schedules/", + "launch": "/api/v2/workflow_job_templates/47/launch/", + "webhook_key": "/api/v2/workflow_job_templates/47/webhook_key/", + "webhook_receiver": "/api/v2/workflow_job_templates/47/github/", + "workflow_nodes": "/api/v2/workflow_job_templates/47/workflow_nodes/", + "labels": "/api/v2/workflow_job_templates/47/labels/", + "activity_stream": "/api/v2/workflow_job_templates/47/activity_stream/", + "notification_templates_started": "/api/v2/workflow_job_templates/47/notification_templates_started/", + "notification_templates_success": "/api/v2/workflow_job_templates/47/notification_templates_success/", + "notification_templates_error": "/api/v2/workflow_job_templates/47/notification_templates_error/", + "notification_templates_approvals": "/api/v2/workflow_job_templates/47/notification_templates_approvals/", + "access_list": "/api/v2/workflow_job_templates/47/access_list/", + "object_roles": "/api/v2/workflow_job_templates/47/object_roles/", + "survey_spec": "/api/v2/workflow_job_templates/47/survey_spec/", + "copy": "/api/v2/workflow_job_templates/47/copy/", + "organization": "/api/v2/organizations/3/", + "webhook_credential": "/api/v2/credentials/8/" + }, + "summary_fields": { + "organization": { + "id": 3, + "name": "Mock Org", + "description": "" + }, + "inventory": { + "id": 7, + "name": "Mock Smart Inv", + "description": "", + "has_active_failures": false, + "total_hosts": 1, + "hosts_with_active_failures": 0, + "total_groups": 0, + "has_inventory_sources": false, + "total_inventory_sources": 0, + "inventory_sources_with_failures": 0, + "organization_id": 1, + "kind": "smart" + }, + "last_job": { + "id": 226, + "name": "abc", + "description": "From Tower bulk-data script", + "finished": "2020-04-08T21:30:44.282245Z", + "status": "failed", + "failed": true + }, + "last_update": { + "id": 226, + "name": "abc", + "description": "From Tower bulk-data script", + "status": "failed", + "failed": true + }, + "webhook_credential": { + "id": 8, + "name": "github", + "description": "", + "kind": "github_token", + "cloud": false, + "credential_type_id": 12 + }, + "created_by": { + "id": 8, + "username": "user-2", + "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 workflow job template", + "name": "Admin", + "id": 260 + }, + "execute_role": { + "description": "May run the workflow job template", + "name": "Execute", + "id": 261 + }, + "read_role": { + "description": "May view settings for the workflow job template", + "name": "Read", + "id": 262 + }, + "approval_role": { + "description": "Can approve or deny a workflow approval node", + "name": "Approve", + "id": 263 + } + }, + "user_capabilities": { + "edit": true, + "delete": true, + "start": true, + "schedule": true, + "copy": true + }, + "labels": { + "count": 2, + "results": [ + { + "id": 104, + "name": "L_10o0" + }, + { + "id": 105, + "name": "L_20o0" + } + ] + }, + "recent_jobs": [ + { + "id": 226, + "status": "failed", + "finished": "2020-04-08T21:30:44.282245Z", + "canceled_on": null, + "type": "workflow_job" + } + ] + }, + "created": "2020-04-07T16:38:02.856877Z", + "modified": "2020-04-13T20:53:53.761355Z", + "name": "Mock Workflow", + "description": "Mock WF Description", + "last_job_run": "2020-04-08T21:30:44.282245Z", + "last_job_failed": true, + "next_job_run": null, + "status": "failed", + "extra_vars": "---\nmock: data", + "organization": 3, + "survey_enabled": false, + "allow_simultaneous": true, + "ask_variables_on_launch": false, + "inventory": 7, + "limit": "hosts1,hosts2", + "scm_branch": "/bar/", + "ask_inventory_on_launch": true, + "ask_scm_branch_on_launch": true, + "ask_limit_on_launch": true, + "webhook_service": "github", + "webhook_credential": 8 +} \ No newline at end of file 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 e550de8ae4..c985054e2e 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 @@ -72,6 +72,7 @@ function NodeViewModal({ i18n }) { } = useRequest( useCallback(async () => { let { data } = await nodeAPI?.readDetail(unifiedJobTemplate.id); + if (data?.type === 'job_template') { const { data: { results = [] }, @@ -79,6 +80,13 @@ function NodeViewModal({ i18n }) { data = Object.assign(data, { instance_groups: results }); } + if (data?.related?.webhook_receiver) { + const { + data: { webhook_key }, + } = await nodeAPI?.readWebhookKey(data.id); + data = Object.assign(data, { webhook_key }); + } + 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 6edcfbbd66..7d049d70e1 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 @@ -11,9 +11,23 @@ import NodeViewModal from './NodeViewModal'; jest.mock('@api/models/JobTemplates'); jest.mock('@api/models/WorkflowJobTemplates'); WorkflowJobTemplatesAPI.readLaunch.mockResolvedValue({}); -WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({}); +WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({ + data: { + id: 1, + type: 'workflow_job_template', + related: { + webhook_receiver: '/api/v2/job_templates/7/gitlab/', + }, + }, +}); +WorkflowJobTemplatesAPI.readWebhookKey.mockResolvedValue({ + data: { + webhook_key: 'Pim3mRXT0', + }, +}); JobTemplatesAPI.readLaunch.mockResolvedValue({}); JobTemplatesAPI.readInstanceGroups.mockResolvedValue({}); +JobTemplatesAPI.readWebhookKey.mockResolvedValue({}); JobTemplatesAPI.readDetail.mockResolvedValue({ data: { id: 1, @@ -74,6 +88,7 @@ describe('NodeViewModal', () => { expect(JobTemplatesAPI.readDetail).not.toHaveBeenCalled(); expect(JobTemplatesAPI.readInstanceGroups).not.toHaveBeenCalled(); expect(WorkflowJobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1); + expect(WorkflowJobTemplatesAPI.readWebhookKey).toHaveBeenCalledWith(1); }); test('Close button dispatches as expected', () => { @@ -125,6 +140,7 @@ describe('NodeViewModal', () => { }); waitForLoaded(wrapper); expect(WorkflowJobTemplatesAPI.readLaunch).not.toHaveBeenCalled(); + expect(JobTemplatesAPI.readWebhookKey).not.toHaveBeenCalledWith(); expect(JobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1); expect(JobTemplatesAPI.readDetail).toHaveBeenCalledWith(1); expect(JobTemplatesAPI.readInstanceGroups).toHaveBeenCalledTimes(1); From f957ef724981c89627e0076775ac699efcc8c58a Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 14 Apr 2020 15:07:32 -0400 Subject: [PATCH 2/3] Add webhook fields to wf node job template detail --- .../components/PromptDetail/PromptDetail.jsx | 12 +++-- .../PromptDetail/PromptJobTemplateDetail.jsx | 52 ++++++++++++++----- .../PromptJobTemplateDetail.test.jsx | 16 ++++++ .../PromptDetail/data.job_template.json | 24 ++++++++- 4 files changed, 85 insertions(+), 19 deletions(-) diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx index cfb2745904..26e47d2956 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx @@ -8,6 +8,7 @@ import { toTitleCase } from '@util/strings'; import { Chip, ChipGroup } from '@patternfly/react-core'; import { VariablesDetail } from '@components/CodeMirrorInput'; +import CredentialChip from '@components/CredentialChip'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import PromptProjectDetail from './PromptProjectDetail'; @@ -172,7 +173,6 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) { /> )} - {/* TODO: Add JT, WFJT, Inventory Source Details */} {details?.type === 'project' && ( )} @@ -211,14 +211,16 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) { {overrides?.credentials && ( {overrides.credentials.map(cred => ( - - {cred.name} - + ))} } diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx index bb34806a19..e8f979247b 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx @@ -24,12 +24,14 @@ function PromptJobTemplateDetail({ i18n, resource }) { job_type, limit, playbook, + related, scm_branch, skip_tags, summary_fields, - url, use_fact_cache, verbosity, + webhook_key, + webhook_service, } = resource; const VERBOSITY = { @@ -114,23 +116,49 @@ function PromptJobTemplateDetail({ i18n, resource }) { value={diff_mode ? 'On' : 'Off'} /> - {host_config_key && ( - - - - + + {related?.callback && ( + + )} + + {related.webhook_receiver && ( + + )} + + {summary_fields?.webhook_credential && ( + + } + /> )} {optionsList && } {summary_fields?.credentials?.length > 0 && ( ( - - ))} + value={ + + {summary_fields.credentials.map(cred => ( + + ))} + + } /> )} {summary_fields?.labels?.results?.length > 0 && ( diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx index b33af6c040..30f92824ab 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.test.jsx @@ -5,6 +5,7 @@ import mockData from './data.job_template.json'; const mockJT = { ...mockData, + webhook_key: 'PiM3n2', instance_groups: [ { id: 1, @@ -49,9 +50,24 @@ describe('PromptJobTemplateDetail', () => { assertDetail('Show Changes', 'Off'); assertDetail('Job Slicing', '1'); assertDetail('Host Config Key', 'a1b2c3'); + assertDetail('Webhook Service', 'Github'); + assertDetail('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/') + ); expect( wrapper.find('Detail[label="Provisioning Callback URL"] dd').text() ).toEqual(expect.stringContaining('/api/v2/job_templates/7/callback/')); + expect( + wrapper + .find('Detail[label="Webhook Credential"]') + .containsAllMatchingElements([ + + Github Token:GitHub Cred + , + ]) + ).toEqual(true); expect( wrapper.find('Detail[label="Credentials"]').containsAllMatchingElements([ diff --git a/awx/ui_next/src/components/PromptDetail/data.job_template.json b/awx/ui_next/src/components/PromptDetail/data.job_template.json index 34dfc47154..0f29f134d2 100644 --- a/awx/ui_next/src/components/PromptDetail/data.job_template.json +++ b/awx/ui_next/src/components/PromptDetail/data.job_template.json @@ -16,6 +16,8 @@ "schedules": "/api/v2/job_templates/7/schedules/", "activity_stream": "/api/v2/job_templates/7/activity_stream/", "launch": "/api/v2/job_templates/7/launch/", + "webhook_key": "/api/v2/job_templates/7/webhook_key/", + "webhook_receiver": "/api/v2/job_templates/7/github/", "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/", @@ -24,7 +26,9 @@ "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/" + "copy": "/api/v2/job_templates/7/copy/", + "callback": "/api/v2/job_templates/7/callback/", + "webhook_credential": "/api/v2/credentials/8/" }, "summary_fields": { "inventory": { @@ -64,6 +68,14 @@ "status": "successful", "failed": false }, + "webhook_credential": { + "id": 8, + "name": "GitHub Cred", + "description": "", + "kind": "github_token", + "cloud": false, + "credential_type_id": 12 + }, "created_by": { "id": 1, "username": "admin", @@ -123,6 +135,12 @@ "status": "successful", "finished": "2019-10-01T14:34:35.142483Z", "type": "job" + }, + { + "id": 13, + "status": "successful", + "finished": "2019-10-01T14:34:35.142483Z", + "type": "job" } ], "extra_credentials": [], @@ -174,5 +192,7 @@ "diff_mode": false, "allow_simultaneous": true, "custom_virtualenv": null, - "job_slice_count": 1 + "job_slice_count": 1, + "webhook_service": "github", + "webhook_credential": 8 } \ No newline at end of file From 2524e8af47db9453f25beca655565bbe269abf93 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 14 Apr 2020 15:08:37 -0400 Subject: [PATCH 3/3] Separate prompted modal section with divider and fix user word-wrap --- awx/ui_next/src/components/DetailList/UserDateDetail.jsx | 7 ++++++- awx/ui_next/src/components/PromptDetail/PromptDetail.jsx | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/awx/ui_next/src/components/DetailList/UserDateDetail.jsx b/awx/ui_next/src/components/DetailList/UserDateDetail.jsx index b16e03c147..f65ab665ed 100644 --- a/awx/ui_next/src/components/DetailList/UserDateDetail.jsx +++ b/awx/ui_next/src/components/DetailList/UserDateDetail.jsx @@ -2,10 +2,15 @@ import React from 'react'; import { node, string } from 'prop-types'; import { Trans } from '@lingui/macro'; import { Link } from 'react-router-dom'; +import styled from 'styled-components'; import { formatDateString } from '@util/dates'; -import Detail from './Detail'; +import _Detail from './Detail'; import { SummaryFieldUser } from '../../types'; +const Detail = styled(_Detail)` + word-break: break-word; +`; + function UserDateDetail({ label, date, user, dataCy = null }) { const dateStr = formatDateString(date); const username = user ? user.username : ''; diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx index 26e47d2956..e2aa73ec6e 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx @@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { toTitleCase } from '@util/strings'; -import { Chip, ChipGroup } from '@patternfly/react-core'; +import { Chip, ChipGroup, Divider } from '@patternfly/react-core'; import { VariablesDetail } from '@components/CodeMirrorInput'; import CredentialChip from '@components/CredentialChip'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; @@ -200,6 +200,7 @@ function PromptDetail({ i18n, resource, launchConfig = {} }) { {hasPromptData(launchConfig) && hasOverrides && ( <> + {i18n._(t`Prompted Values`)} {overrides?.job_type && (