diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
index b64cb06822..4e013c87f2 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
+++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.jsx
@@ -28,12 +28,16 @@ function hasPromptData(launchData) {
}
function formatTimeout(timeout) {
- if (typeof timeout === "undefined" || timeout === null) {
+ 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>;
+ return (
+ <>
+ {minutes} min {seconds} sec
+ >
+ );
}
function PromptDetail({ i18n, resource, launchConfig = {} }) {
diff --git a/awx/ui_next/src/components/Workflow/workflowReducer.js b/awx/ui_next/src/components/Workflow/workflowReducer.js
index 05d8af15fb..1fe635c47e 100644
--- a/awx/ui_next/src/components/Workflow/workflowReducer.js
+++ b/awx/ui_next/src/components/Workflow/workflowReducer.js
@@ -17,6 +17,7 @@ export function initReducer() {
nodes: [],
nodeToDelete: null,
nodeToEdit: null,
+ nodeToView: null,
showDeleteAllNodesModal: false,
showLegend: false,
showTools: false,
@@ -93,6 +94,8 @@ export default function visualizerReducer(state, action) {
return updateLink(state, action.linkType);
case 'UPDATE_NODE':
return updateNode(state, action.node);
+ case 'REFRESH_NODE':
+ return refreshNode(state, action.node);
default:
throw new Error(`Unrecognized action type: ${action.type}`);
}
@@ -607,3 +610,17 @@ function updateNode(state, editedNode) {
unsavedChanges: true,
};
}
+
+function refreshNode(state, refreshedNode) {
+ const { nodeToView, nodes } = state;
+ const newNodes = [...nodes];
+
+ const matchingNode = newNodes.find(node => node.id === nodeToView.id);
+ matchingNode.unifiedJobTemplate = refreshedNode.nodeResource;
+
+ return {
+ ...state,
+ nodes: newNodes,
+ nodeToView: matchingNode,
+ };
+}
diff --git a/awx/ui_next/src/components/Workflow/workflowReducer.test.js b/awx/ui_next/src/components/Workflow/workflowReducer.test.js
index 82aa87cf09..cab793ee5c 100644
--- a/awx/ui_next/src/components/Workflow/workflowReducer.test.js
+++ b/awx/ui_next/src/components/Workflow/workflowReducer.test.js
@@ -16,6 +16,7 @@ const defaultState = {
nodes: [],
nodeToDelete: null,
nodeToEdit: null,
+ nodeToView: null,
showDeleteAllNodesModal: false,
showLegend: false,
showTools: false,
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 03bea68be7..d233adc03c 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
@@ -11,44 +11,105 @@ import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import PromptDetail from '@components/PromptDetail';
import useRequest from '@util/useRequest';
-import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '@api';
+import {
+ InventorySourcesAPI,
+ JobTemplatesAPI,
+ ProjectsAPI,
+ WorkflowJobTemplatesAPI,
+} from '@api';
+
+function getNodeType(node) {
+ const ujtType = node.type || node.unified_job_type;
+
+ let nodeType;
+ let nodeAPI;
+ switch (ujtType) {
+ case 'job_template':
+ case 'job':
+ nodeType = 'job_template';
+ nodeAPI = JobTemplatesAPI;
+ break;
+ case 'project':
+ case 'project_update':
+ nodeType = 'project_sync';
+ nodeAPI = ProjectsAPI;
+ break;
+ case 'inventory_source':
+ case 'inventory_update':
+ nodeType = 'inventory_source_sync';
+ nodeAPI = InventorySourcesAPI;
+ break;
+ case 'workflow_job_template':
+ case 'workflow_job':
+ nodeType = 'workflow_job_template';
+ nodeAPI = WorkflowJobTemplatesAPI;
+ break;
+ case 'workflow_approval_template':
+ case 'workflow_approval':
+ nodeType = 'approval';
+ nodeAPI = null;
+ break;
+ default:
+ }
+ return [nodeType, nodeAPI];
+}
function NodeViewModal({ i18n }) {
const dispatch = useContext(WorkflowDispatchContext);
const { nodeToView } = useContext(WorkflowStateContext);
const { unifiedJobTemplate } = nodeToView;
- const jobType =
- unifiedJobTemplate.unified_job_type || unifiedJobTemplate.type;
+ const [nodeType, nodeAPI] = getNodeType(unifiedJobTemplate);
const {
result: launchConfig,
- isLoading,
- error,
+ isLoading: isLaunchConfigLoading,
+ error: launchConfigError,
request: fetchLaunchConfig,
} = useRequest(
useCallback(async () => {
- const readLaunch = ['workflow_job', 'workflow_job_template'].includes(
- jobType
- )
- ? WorkflowJobTemplatesAPI.readLaunch(unifiedJobTemplate.id)
- : JobTemplatesAPI.readLaunch(unifiedJobTemplate.id);
-
+ const readLaunch =
+ nodeType === 'workflow_job_template'
+ ? WorkflowJobTemplatesAPI.readLaunch(unifiedJobTemplate.id)
+ : JobTemplatesAPI.readLaunch(unifiedJobTemplate.id);
const { data } = await readLaunch;
-
return data;
- }, [jobType, unifiedJobTemplate]),
+ }, [nodeType, unifiedJobTemplate.id]),
{}
);
+ const {
+ result: nodeDetail,
+ isLoading: isNodeDetailLoading,
+ error: nodeDetailError,
+ request: fetchNodeDetail,
+ } = useRequest(
+ useCallback(async () => {
+ const { data } = await nodeAPI?.readDetail(unifiedJobTemplate.id);
+ return data;
+ }, [nodeAPI, unifiedJobTemplate.id]),
+ null
+ );
+
useEffect(() => {
- if (
- ['workflow_job', 'workflow_job_template', 'job', 'job_template'].includes(
- jobType
- )
- ) {
+ if (nodeType === 'workflow_job_template' || nodeType === 'job_template') {
fetchLaunchConfig();
}
- }, [jobType, fetchLaunchConfig]);
+
+ if (unifiedJobTemplate.unified_job_type && nodeType !== 'approval') {
+ fetchNodeDetail();
+ }
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ useEffect(() => {
+ if (nodeDetail) {
+ dispatch({
+ type: 'REFRESH_NODE',
+ node: {
+ nodeResource: nodeDetail,
+ },
+ });
+ }
+ }, [nodeDetail]); // eslint-disable-line react-hooks/exhaustive-deps
const handleEdit = () => {
dispatch({ type: 'SET_NODE_TO_VIEW', value: null });
@@ -56,11 +117,10 @@ function NodeViewModal({ i18n }) {
};
let Content;
-
- if (isLoading) {
+ if (isLaunchConfigLoading || isNodeDetailLoading) {
Content = ;
- } else if (error) {
- Content = ;
+ } else if (launchConfigError || nodeDetailError) {
+ Content = ;
} else {
Content = (
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 f0a9fc5456..9026d1f575 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,6 +11,7 @@ import NodeViewModal from './NodeViewModal';
jest.mock('@api/models/JobTemplates');
jest.mock('@api/models/WorkflowJobTemplates');
WorkflowJobTemplatesAPI.readLaunch.mockResolvedValue({});
+WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({});
JobTemplatesAPI.readLaunch.mockResolvedValue({});
const dispatch = jest.fn();