From 231cccbb19a657b2227d32bc5f04004c3ace6ddf Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 2 Jul 2021 16:07:55 -0400 Subject: [PATCH] Adds support for workflow node aliasing via identifier field --- .../components/Workflow/WorkflowNodeHelp.js | 28 ++- .../Workflow/WorkflowNodeHelp.test.js | 43 ++-- .../components/Workflow/workflowReducer.js | 6 +- .../WorkflowOutputGraph.test.js | 19 +- .../Job/WorkflowOutput/WorkflowOutputNode.js | 25 ++- .../Job/WorkflowOutput/useWsWorkflowOutput.js | 1 - .../Modals/NodeModals/NodeAddModal.js | 2 + .../Modals/NodeModals/NodeEditModal.js | 3 + .../Modals/NodeModals/NodeModal.test.js | 4 + .../NodeModals/NodeTypeStep/NodeTypeStep.js | 212 +++++++++--------- .../NodeTypeStep/useNodeTypeStep.js | 24 +- .../Modals/NodeModals/useWorkflowNodeSteps.js | 25 ++- .../Visualizer.js | 26 +++ .../VisualizerGraph.test.js | 16 +- .../VisualizerNode.js | 24 +- awx/ui/src/util/strings.js | 5 + 16 files changed, 310 insertions(+), 153 deletions(-) diff --git a/awx/ui/src/components/Workflow/WorkflowNodeHelp.js b/awx/ui/src/components/Workflow/WorkflowNodeHelp.js index 0d6c61a01c..7c6122aa7c 100644 --- a/awx/ui/src/components/Workflow/WorkflowNodeHelp.js +++ b/awx/ui/src/components/Workflow/WorkflowNodeHelp.js @@ -6,6 +6,7 @@ import styled from 'styled-components'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import { shape } from 'prop-types'; import { secondsToHHMMSS } from 'util/dates'; +import { stringIsUUID } from 'util/strings'; const GridDL = styled.dl` column-gap: 15px; @@ -37,6 +38,11 @@ function WorkflowNodeHelp({ node }) { const unifiedJobTemplate = node?.fullUnifiedJobTemplate || node?.originalNodeObject?.summary_fields?.unified_job_template; + const identifier = + node?.identifier || + (!stringIsUUID(node?.originalNodeObject?.identifier) + ? node.originalNodeObject.identifier + : null); if (unifiedJobTemplate || job) { const type = unifiedJobTemplate ? unifiedJobTemplate.unified_job_type || unifiedJobTemplate.type @@ -132,10 +138,18 @@ function WorkflowNodeHelp({ node }) { )} {job && ( + {identifier && ( + <> +
+ {t`Node Alias`} +
+
{identifier}
+ + )}
- {t`Name`} + {t`Resource Name`}
-
{job.name}
+
{unifiedJobTemplate.name}
{t`Type`}
@@ -158,8 +172,16 @@ function WorkflowNodeHelp({ node }) { )} {unifiedJobTemplate && !job && ( + {identifier && ( + <> +
+ {t`Node Alias`} +
+
{identifier}
+ + )}
- {t`Name`} + {t`Resource Name`}
{unifiedJobTemplate.name}
diff --git a/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js b/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js index 9ac9afbacd..dbb74eac7e 100644 --- a/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js +++ b/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js @@ -2,29 +2,32 @@ import React from 'react'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import WorkflowNodeHelp from './WorkflowNodeHelp'; -describe('WorkflowNodeHelp', () => { - test('successfully mounts', () => { - const wrapper = mountWithContexts(); - expect(wrapper).toHaveLength(1); - }); - test('renders the expected content for a completed job template job', () => { - const node = { - originalNodeObject: { - summary_fields: { - job: { - name: 'Foo Job Template', - elapsed: 9000, - status: 'successful', - type: 'job', - }, - }, - }, - unifiedJobTemplate: { +const node = { + originalNodeObject: { + identifier: 'Foo', + summary_fields: { + job: { name: 'Foo Job Template', - unified_job_type: 'job', + elapsed: 9000, + status: 'successful', + type: 'job', }, - }; + unified_job_template: { + name: 'Foo Job Template', + type: 'job_template', + }, + }, + }, + unifiedJobTemplate: { + name: 'Foo Job Template', + unified_job_type: 'job', + }, +}; + +describe('WorkflowNodeHelp', () => { + test('renders the expected content for a completed job template job', () => { const wrapper = mountWithContexts(); + expect(wrapper.find('#workflow-node-help-alias').text()).toBe('Foo'); expect(wrapper.find('#workflow-node-help-name').text()).toBe( 'Foo Job Template' ); diff --git a/awx/ui/src/components/Workflow/workflowReducer.js b/awx/ui/src/components/Workflow/workflowReducer.js index 4d2dbc0dac..0d1c729967 100644 --- a/awx/ui/src/components/Workflow/workflowReducer.js +++ b/awx/ui/src/components/Workflow/workflowReducer.js @@ -184,6 +184,7 @@ function createNode(state, node) { isInvalidLinkTarget: false, promptValues: node.promptValues, all_parents_must_converge: node.all_parents_must_converge, + identifier: node.identifier, }); // Ensures that root nodes appear to always run @@ -660,17 +661,16 @@ function updateNode(state, editedNode) { launchConfig, promptValues, all_parents_must_converge, + identifier, } = editedNode; const newNodes = [...nodes]; const matchingNode = newNodes.find((node) => node.id === nodeToEdit.id); matchingNode.all_parents_must_converge = all_parents_must_converge; - if (matchingNode.originalNodeObject) { - delete matchingNode.originalNodeObject.all_parents_must_converge; - } matchingNode.fullUnifiedJobTemplate = nodeResource; matchingNode.isEdited = true; matchingNode.launchConfig = launchConfig; + matchingNode.identifier = identifier; if (promptValues) { matchingNode.promptValues = promptValues; diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js index 7a8f58e1d3..1e8828a176 100644 --- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js +++ b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js @@ -65,6 +65,7 @@ const workflowContext = { { id: 2, originalNodeObject: { + identifier: 'Node identifier', summary_fields: { job: { name: 'Foo JT', @@ -72,6 +73,10 @@ const workflowContext = { status: 'successful', elapsed: 60, }, + unified_job_template: { + name: 'Foo JT', + type: 'job_template', + }, }, }, }, @@ -185,9 +190,17 @@ describe('WorkflowOutputGraph', () => { expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0); wrapper.find('g#node-2').simulate('mouseenter'); expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(1); - expect(wrapper.find('WorkflowNodeHelp').contains(Name)).toEqual( - true - ); + expect( + wrapper.find('WorkflowNodeHelp').contains(Node Alias) + ).toEqual(true); + expect( + wrapper + .find('WorkflowNodeHelp') + .containsMatchingElement(
Node identifier
) + ).toEqual(true); + expect( + wrapper.find('WorkflowNodeHelp').contains(Resource Name) + ).toEqual(true); expect( wrapper.find('WorkflowNodeHelp').containsMatchingElement(
Foo JT
) ).toEqual(true); diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js index d00a4b98c1..153a14b6c1 100644 --- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js +++ b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js @@ -8,6 +8,7 @@ import { WorkflowStateContext } from 'contexts/Workflow'; import StatusIcon from 'components/StatusIcon'; import { WorkflowNodeTypeLetter } from 'components/Workflow'; import { secondsToHHMMSS } from 'util/dates'; +import { stringIsUUID } from 'util/strings'; import { constants as wfConstants } from 'components/Workflow/WorkflowUtils'; const NodeG = styled.g` @@ -67,9 +68,6 @@ function WorkflowOutputNode({ mouseEnter, mouseLeave, node }) { const history = useHistory(); const { nodePositions } = useContext(WorkflowStateContext); const job = node?.originalNodeObject?.summary_fields?.job; - const jobName = - node?.originalNodeObject?.summary_fields?.unified_job_template?.name || - node?.unifiedJobTemplate?.name; let borderColor = '#93969A'; @@ -94,6 +92,23 @@ function WorkflowOutputNode({ mouseEnter, mouseLeave, node }) { } }; + let nodeName; + + if ( + node?.identifier || + (node?.originalNodeObject?.identifier && + !stringIsUUID(node.originalNodeObject.identifier)) + ) { + nodeName = node?.identifier + ? node?.identifier + : node?.originalNodeObject?.identifier; + } else { + nodeName = + node?.fullUnifiedJobTemplate?.name || + node?.originalNodeObject?.summary_fields?.unified_job_template?.name || + t`DELETED`; + } + return ( {job.status !== 'pending' && } -

{jobName}

+

{nodeName}

{!!job?.elapsed && ( {secondsToHHMMSS(job.elapsed)} )} ) : ( - {jobName || t`DELETED`} + {nodeName} )} diff --git a/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js b/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js index b5edb8e036..ee8f794bb7 100644 --- a/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js +++ b/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js @@ -102,7 +102,6 @@ function updateNode(nodes, index, message) { job: { ...nodes[index]?.job, id: message.unified_job_id, - name: nodes[index]?.job?.name || nodes[index]?.unifiedJobTemplate?.name, status: message.status, type: message.type, }, diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js index ac37f0e562..4704c165f3 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js @@ -20,6 +20,7 @@ function NodeAddModal() { timeoutSeconds, linkType, convergence, + identifier, } = values; if (values) { @@ -35,6 +36,7 @@ function NodeAddModal() { const node = { linkType, all_parents_must_converge: convergence === 'all', + identifier, }; delete values.convergence; diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js index 762f07aeed..23053a2375 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js @@ -18,6 +18,7 @@ function NodeEditModal() { timeoutMinutes, timeoutSeconds, convergence, + identifier, ...rest } = values; let node; @@ -30,11 +31,13 @@ function NodeEditModal() { timeout: Number(timeoutMinutes) * 60 + Number(timeoutSeconds), type: 'workflow_approval_template', }, + identifier, }; } else { node = { nodeResource, all_parents_must_converge: convergence === 'all', + identifier, }; if (nodeType === 'job_template' || nodeType === 'workflow_job_template') { node.promptValues = { diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.js index 30fbe5501c..bdb27196e7 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.js @@ -536,6 +536,7 @@ describe('Edit existing node', () => { value={{ nodeToEdit: { id: 2, + identifier: 'Foo', fullUnifiedJobTemplate: { id: 1, name: 'Test Project', @@ -602,6 +603,7 @@ describe('Edit existing node', () => { expect(onSave).toBeCalledWith( { convergence: 'any', + identifier: 'Foo', approvalDescription: 'Test Approval Description', approvalName: 'Test Approval', linkType: 'success', @@ -622,6 +624,7 @@ describe('Edit existing node', () => { value={{ nodeToEdit: { id: 2, + identifier: 'Foo', fullUnifiedJobTemplate: { id: 1, name: 'Test Approval', @@ -672,6 +675,7 @@ describe('Edit existing node', () => { expect(onSave).toBeCalledWith( { convergence: 'any', + identifier: 'Foo', linkType: 'success', nodeResource: { id: 1, diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js index 9df1047ffd..c1594539ba 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js @@ -31,7 +31,7 @@ const NodeTypeErrorAlert = styled(Alert)` `; const TimeoutInput = styled(TextInput)` - width: 200px; + width: 200px !important; :not(:first-of-type) { margin-left: 20px; } @@ -43,7 +43,7 @@ const TimeoutLabel = styled.p` min-width: fit-content; `; -function NodeTypeStep() { +function NodeTypeStep({ isIdentifierRequired }) { const [nodeTypeField, , nodeTypeHelpers] = useField('nodeType'); const [nodeResourceField, nodeResourceMeta, nodeResourceHelpers] = useField('nodeResource'); @@ -157,105 +157,117 @@ function NodeTypeStep() { )}
- {nodeTypeField.value === 'workflow_approval_template' && ( - - - - -
- { - timeoutMinutesField.onChange(event); - }} - step="1" - type="number" - /> - - min - - { - timeoutSecondsField.onChange(event); - }} - step="1" - type="number" - /> - - sec - -
-
-
- )} - - {t`Preconditions for running this node when there are multiple parents. Refer to the`}{' '} - - {t`documentation`} - {' '} - {t`for more info.`} - - } - /> - } - > - - + + + +
diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js index 230e55bcd3..5ffc8d75c5 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js @@ -2,14 +2,16 @@ import React from 'react'; import { t } from '@lingui/macro'; import { useField } from 'formik'; import StepName from 'components/LaunchPrompt/steps/StepName'; +import { stringIsUUID } from 'util/strings'; import NodeTypeStep from './NodeTypeStep'; const STEP_ID = 'nodeType'; -export default function useNodeTypeStep() { +export default function useNodeTypeStep(nodeToEdit) { const [, meta] = useField('nodeType'); const [approvalNameField] = useField('approvalName'); const [nodeTypeField, ,] = useField('nodeType'); + const [, identifierMeta] = useField('identifier'); const [nodeResourceField, nodeResourceMeta] = useField({ name: 'nodeResource', validate: (value) => { @@ -26,14 +28,16 @@ export default function useNodeTypeStep() { }, }); - const formError = !!meta.error || !!nodeResourceMeta.error; + const formError = + !!meta.error || !!nodeResourceMeta.error || identifierMeta.error; return { step: getStep( nodeTypeField, approvalNameField, nodeResourceField, - formError + formError, + nodeToEdit ), initialValues: getInitialValues(), isReady: true, @@ -49,7 +53,8 @@ function getStep( nodeTypeField, approvalNameField, nodeResourceField, - formError + formError, + nodeToEdit ) { const isEnabled = () => { if ( @@ -70,7 +75,15 @@ function getStep( {t`Node type`} ), - component: , + component: ( + + ), enableNext: isEnabled(), }; } @@ -83,5 +96,6 @@ function getInitialValues() { timeoutSeconds: 0, nodeType: 'job_template', convergence: 'any', + identifier: '', }; } diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js index c91313952d..81addb2463 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js @@ -8,6 +8,7 @@ import useSurveyStep from 'components/LaunchPrompt/steps/useSurveyStep'; import usePreviewStep from 'components/LaunchPrompt/steps/usePreviewStep'; import { WorkflowStateContext } from 'contexts/Workflow'; import { jsonToYaml } from 'util/yaml'; +import { stringIsUUID } from 'util/strings'; import useNodeTypeStep from './NodeTypeStep/useNodeTypeStep'; import useDaysToKeepStep from './useDaysToKeepStep'; import useRunTypeStep from './useRunTypeStep'; @@ -37,6 +38,22 @@ const getNodeToEditDefaultValues = ( nodeToEdit, resourceDefaultCredentials ) => { + let identifier = ''; + if ( + Object.prototype.hasOwnProperty.call(nodeToEdit, 'identifier') && + nodeToEdit.identifier !== null + ) { + ({ identifier } = nodeToEdit); + } else if ( + Object.prototype.hasOwnProperty.call( + nodeToEdit?.originalNodeObject, + 'identifier' + ) && + !stringIsUUID(nodeToEdit?.originalNodeObject?.identifier) + ) { + identifier = nodeToEdit?.originalNodeObject?.identifier; + } + const initialValues = { nodeResource: nodeToEdit?.fullUnifiedJobTemplate || null, nodeType: nodeToEdit?.fullUnifiedJobTemplate?.type || 'job_template', @@ -45,6 +62,7 @@ const getNodeToEditDefaultValues = ( nodeToEdit?.originalNodeObject?.all_parents_must_converge ? 'all' : 'any', + identifier, }; if ( @@ -274,6 +292,8 @@ export default function useWorkflowNodeSteps( }), {} ); + initialValues.identifier = formikValues.identifier; + initialValues.convergence = formikValues.convergence; } const errors = formikErrors.nodeResource @@ -289,15 +309,10 @@ export default function useWorkflowNodeSteps( errors.nodeResource = t`Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes`; } - if (initialValues.convergence === 'all') { - formikValues.convergence = 'all'; - } - resetForm({ errors, values: { ...initialValues, - convergence: formikValues.convergence, nodeResource: formikValues.nodeResource, nodeType: formikValues.nodeType, linkType: formikValues.linkType, diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js index a028822558..a01d8bde11 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js @@ -9,6 +9,7 @@ import { WorkflowStateContext, } from 'contexts/Workflow'; import { getAddedAndRemoved } from 'util/lists'; +import { stringIsUUID } from 'util/strings'; import AlertModal from 'components/AlertModal'; import ErrorDetail from 'components/ErrorDetail'; import { layoutGraph } from 'components/Workflow/WorkflowUtils'; @@ -51,6 +52,20 @@ const Wrapper = styled.div` height: 100%; `; +const replaceIdentifier = node => { + if (stringIsUUID(node.originalNodeObject.identifier) && node.identifier) { + return true; + } + + if ( + !stringIsUUID(node.originalNodeObject.identifier) && + node.identifier !== node.originalNodeObject.identifier + ) { + return true; + } + + return false; +}; const getAggregatedCredentials = ( originalNodeOverride = [], templateDefaultCredentials = [] @@ -366,6 +381,7 @@ function Visualizer({ template }) { nodeRequests.push( WorkflowJobTemplatesAPI.createNode(template.id, { all_parents_must_converge: node.all_parents_must_converge, + ...(node.identifier && { identifier: node.identifier }), }).then(({ data }) => { node.originalNodeObject = data; originalLinkMap[node.id] = { @@ -390,6 +406,7 @@ function Visualizer({ template }) { inventory: node.promptValues?.inventory?.id || null, unified_job_template: node.fullUnifiedJobTemplate.id, all_parents_must_converge: node.all_parents_must_converge, + ...(node.identifier && { identifier: node.identifier }), }).then(({ data }) => { node.originalNodeObject = data; originalLinkMap[node.id] = { @@ -425,6 +442,9 @@ function Visualizer({ template }) { node.originalNodeObject.id, { all_parents_must_converge: node.all_parents_must_converge, + ...(replaceIdentifier(node) && { + identifier: node.identifier, + }), } ).then(({ data }) => { node.originalNodeObject = data; @@ -447,6 +467,9 @@ function Visualizer({ template }) { node.originalNodeObject.id, { all_parents_must_converge: node.all_parents_must_converge, + ...(replaceIdentifier(node) && { + identifier: node.identifier, + }), } ).then(({ data }) => { node.originalNodeObject = data; @@ -470,6 +493,9 @@ function Visualizer({ template }) { inventory: node.promptValues?.inventory?.id || null, unified_job_template: node.fullUnifiedJobTemplate.id, all_parents_must_converge: node.all_parents_must_converge, + ...(replaceIdentifier(node) && { + identifier: node.identifier, + }), }).then(() => { const { added: addedCredentials, removed: removedCredentials } = getAddedAndRemoved( diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.test.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.test.js index b40751c8e2..9b6d976e69 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.test.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.test.js @@ -68,15 +68,19 @@ const workflowContext = { name: 'Foo JT', type: 'job_template', }, + identifier: 'node 2', }, { id: 3, + identifier: 'node 3', }, { id: 4, + identifier: 'node 4', }, { id: 5, + identifier: 'node 5', }, ], showLegend: false, @@ -183,9 +187,15 @@ describe('VisualizerGraph', () => { .first() .simulate('mouseenter'); expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(1); - expect(wrapper.find('WorkflowNodeHelp').contains(Name)).toEqual( - true - ); + expect( + wrapper.find('WorkflowNodeHelp').contains(Node Alias) + ).toEqual(true); + expect( + wrapper.find('WorkflowNodeHelp').containsMatchingElement(
node 2
) + ).toEqual(true); + expect( + wrapper.find('WorkflowNodeHelp').contains(Resource Name) + ).toEqual(true); expect( wrapper.find('WorkflowNodeHelp').containsMatchingElement(
Foo JT
) ).toEqual(true); diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js index 3f52828253..da1e65f472 100644 --- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js +++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js @@ -1,6 +1,5 @@ import React, { useContext, useRef, useState } from 'react'; import styled from 'styled-components'; - import { t } from '@lingui/macro'; import { bool, func, shape } from 'prop-types'; import { @@ -18,6 +17,7 @@ import AlertModal from 'components/AlertModal'; import ErrorDetail from 'components/ErrorDetail'; import { WorkflowJobTemplateNodesAPI } from 'api'; import { constants as wfConstants } from 'components/Workflow/WorkflowUtils'; +import { stringIsUUID } from 'util/strings'; import { WorkflowActionTooltip, WorkflowActionTooltipItem, @@ -168,6 +168,23 @@ function VisualizerNode({ } }; + let nodeName; + + if ( + node?.identifier || + (node?.originalNodeObject?.identifier && + !stringIsUUID(node.originalNodeObject.identifier)) + ) { + nodeName = node?.identifier + ? node?.identifier + : node?.originalNodeObject?.identifier; + } else { + nodeName = + node?.fullUnifiedJobTemplate?.name || + node?.originalNodeObject?.summary_fields?.unified_job_template?.name || + t`DELETED`; + } + const viewDetailsAction = ( - {node?.fullUnifiedJobTemplate?.name || - node?.originalNodeObject?.summary_fields?.unified_job_template - ?.name || - t`DELETED`} + {nodeName} diff --git a/awx/ui/src/util/strings.js b/awx/ui/src/util/strings.js index ec07cd5384..2eee9bbe96 100644 --- a/awx/ui/src/util/strings.js +++ b/awx/ui/src/util/strings.js @@ -12,3 +12,8 @@ export const toTitleCase = (string) => { export const arrayToString = (value) => value.join(','); export const stringToArray = (value) => value.split(',').filter((val) => !!val); + +export const stringIsUUID = (value) => + /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi.test( + value + );