ui-modules/app-inspector/app/components/workflow/workflow-step.directive.js (221 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import template from "./workflow-step.template.html"; import angular from "angular"; import jsyaml from 'js-yaml'; const MODULE_NAME = 'inspector.workflow-step'; angular.module(MODULE_NAME, []) .directive('workflowStep', workflowStepDirective); export default MODULE_NAME; let count = 0; export function workflowStepDirective() { return { template: template, restrict: 'E', scope: { workflow: '=', task: '=?', step: '<', // definition stepIndex: '<', expanded: '=', onSizeChange: '=', }, controller: ['$sce', '$scope', 'entityApi', controller], controllerAs: 'vm', }; function controller($sce, $scope, entityApi) { try { let observers = []; $scope.$on('$destroy', ()=> { observers.forEach((observer)=> { observer.unsubscribe(); }); }); let vm = this; let step = $scope.step; let index = $scope.stepIndex; $scope.workflowId = ($scope.workflow && $scope.workflow.data || {}).workflowId; vm.toggleExpandState = () => { $scope.expanded = !$scope.expanded; if ($scope.onSizeChange) $scope.onSizeChange(); } vm.stringify = stringify; vm.yaml = (data) => jsyaml.dump(data); vm.yamlOrPrimitive = (data) => typeof data === "string" ? data : vm.yaml(data); vm.nonEmpty = (data) => data && (data.length || Object.keys(data).length); vm.isNullish = _.isNil; vm.getWorkflowNameFromReference = (ref, suffixIfFound) => { if (ref) { if (ref.workflowName) return ref.workflowName; if ($scope.task && $scope.task.children) { var matchingChild = $scope.task.children.find(c => c.metadata && c.metadata.id === ref.workflowId); if (matchingChild && matchingChild.metadata.taskName) { return matchingChild.metadata.taskName + (suffixIfFound ? " " + suffixIfFound + " " : ""); } } } return null; }; vm.hasInterestingWorkflowNameFromReference = (ref, suffixIfFound) => { const wn = vm.getWorkflowNameFromReference(ref, suffixIfFound); return wn && wn.toLowerCase()!='sub-workflow' && !wn.endsWith(' (sub-workflow)'); }; vm.classForCodeMaybeMultiline = (obj) => { let os = vm.yamlOrPrimitive(obj); if (!os) return "simple-code"; os = ''+os; if (os.endsWith('\n')) os = os.substring(0, os.length-1); const lines = os.split('\n'); if (lines.length==1) return "simple-code"; if (lines.length <= 5) return "multiline-code lines-"+lines.length; return "multiline-code multiline-code-resizable lines-"+lines.length; }; $scope.addlData = null; $scope.addlMode = null; vm.showAdditional = (title, mode, obj) => { $scope.addlTitle = title; $scope.addlMode = mode; $scope.addlData = obj==undefined ? null : vm.yamlOrPrimitive(obj); } $scope.stepTitle = { index: index+1, }; if (typeof step === 'string') { $scope.stepTitle.code = step; } else { let shorthand = step.userSuppliedShorthand || step.step || step.s || step.shorthand; $scope.stepTitle.code = shorthand; if (!shorthand) { $scope.stepTitle.code = step.shorthandTypeName || step.type || ''; if (!$scope.stepTitle.code) { if (step.steps) $scope.stepTitle.leftCodeAlternative = "nested workflow"; else $scope.stepTitle.leftCodeAlternative = "workflow step"; // odd... } else { if (step.input) $scope.stepTitle.code += ' ...'; } } if (["workflow","subworkflow"].includes($scope.stepTitle.code)) { $scope.stepTitle.code = null; $scope.stepTitle.leftCodeAlternative = "nested workflow"; } if (step.name) { $scope.stepTitle.name = step.name; } if (step.id) { $scope.stepTitle.id = step.id; } } function gatherOutputAndScratchForStep(osi, result, visited, isPrevious) { if (result.output!=undefined && result.workflowScratch!=undefined) return ; if (osi==undefined) { if (result.workflowScratchPartial != undefined) result.workflowScratch = result.workflowScratchPartial; delete result['workflowScratchPartial']; return; } // osi.woSc let output = osi.context && osi.context.output; if (isPrevious && output != undefined && result.output == undefined) result.output = output; if (isPrevious && osi.workflowScratchUpdates != undefined) result.workflowScratchPartial = Object.assign({}, osi.workflowScratchUpdates, result.workflowScratchPartial); if (osi.workflowScratch != undefined) result.workflowScratchPartial = Object.assign({}, osi.workflowScratchUpdates, result.workflowScratchPartial); let next = undefined; if (osi.previous && osi.previous.length) { const pp = osi.previous[0]; if (!visited.includes(pp)) { visited.push(pp); next = $scope.workflow.data.oldStepInfo[pp]; } } gatherOutputAndScratchForStep(next, result, visited, true); } const resultForStep = {}; vm.getOutputAndScratchForStep = (i) => { if (!resultForStep[i]) { if (!$scope.workflow || !$scope.workflow.data || !$scope.workflow.data.oldStepInfo) return {}; const osi = $scope.workflow.data.oldStepInfo[i] || null; if (!osi) return {}; resultForStep[i] = {}; gatherOutputAndScratchForStep(osi, resultForStep[i], [i], false); } return resultForStep[i]; } function updateData() { let workflow = $scope.workflow; workflow.data = workflow.data || {}; $scope.workflowStepClasses = []; if (workflow.data.currentStepIndex === index) $scope.workflowStepClasses.push('current-step'); $scope.isRunning = (workflow.data.status === 'RUNNING'); $scope.isCurrentMaybeInactive = (workflow.data.currentStepIndex === index); $scope.isCurrentAndActive = ($scope.isCurrentMaybeInactive && $scope.isRunning); $scope.isWorkflowError = (workflow.data.status && workflow.data.status.startsWith('ERROR')); $scope.osi = workflow.data.oldStepInfo[index] || {}; $scope.stepContext = ($scope.isCurrentMaybeInactive ? workflow.data.currentStepInstance : $scope.osi.context) || {}; $scope.isFocusStep = $scope.workflow.tag && ($scope.workflow.tag.stepIndex === index); $scope.isFocusTask = false; $scope.isErrorHandler = $scope.workflow.tag && ($scope.workflow.tag.errorHandlerForTask); // for switch, possibly others -- the step task wraps a chosen step task; // show details for the wrapped chosen task, without showing weird messages $scope.otherMetadata = Object.assign({}, $scope.stepContext.otherMetadata || {}); if ($scope.stepContext.stepState && $scope.stepContext.stepState.selectedStepContext) { $scope.innerStepContext = $scope.stepContext.stepState.selectedStepContext; $scope.outerStepContext = $scope.stepContext; $scope.isWrappingStepTaskOuter = $scope.task && $scope.stepContext.taskId == $scope.task.id; $scope.stepContext = $scope.stepContext.stepState.selectedStepContext; $scope.otherMetadata = Object.assign($scope.otherMetadata, $scope.stepContext.otherMetadata || {}); $scope.isWrappingStepTaskInner = $scope.task && $scope.stepContext.taskId == $scope.task.id; if ($scope.isWrappingStepTaskOuter || $scope.isWrappingStepTaskInner) { $scope.isFocusTask = true; } } if ($scope.task) { if (!vm.isNullish($scope.stepContext.taskId) && $scope.stepContext.taskId === $scope.task.id) { $scope.isFocusTask = true; } else if ($scope.isFocusStep) { // careful -- other instance of this step selected } } $scope.stepCurrentError = ($scope.stepContext.error && !$scope.isFocusTask) ? 'This step had an error.' : ($scope.isWorkflowError && $scope.isCurrentMaybeInactive) ? 'The workflow encountered an error at this step.' /* odd */ : null; const incomplete = $scope.osi.countStarted - $scope.osi.countCompleted > ($scope.isCurrentAndActive ? 1 : 0); $scope.stepCurrentWarning = $scope.stepCurrentError ? null : $scope.stepContext.errorHandlerTaskId ? 'This step had an error which was handled.' : incomplete ? 'This step has previously had an error' : null; $scope.stepCurrentSuccess = $scope.stepCurrentError || $scope.stepCurrentWarning ? null : (!$scope.isCurrentAndActive && !incomplete && $scope.osi.countCompleted > 0) ? 'This step has completed without errors.' : null; } $scope.$watch('workflow', updateData); updateData(); $scope.showStepDefinitionInBody = true; function loadUniqueSubworkflow(workflowTag) { if (!workflowTag) return; return entityApi.getWorkflow(workflowTag.applicationId, workflowTag.entityId, workflowTag.workflowId).then(wResponse => { if (wResponse.data.status === 'RUNNING') { wResponse.interval(1000); observers.push(wResponse.subscribe(() => loadUniqueSubworkflow(workflowTag))); } $scope.uniqueSubworkflow = { applicationId: workflowTag.applicationId, entityId: workflowTag.entityId, workflowTag: workflowTag, data: wResponse.data, }; }).catch(error => { console.log("Unable to load single unique workflow", workflowTag, error); $scope.showStepDefinitionInBody = false; $scope.uniqueSubworkflow = undefined; }); } if (!$scope.workflow.runIsOld && $scope.stepContext.subWorkflows && $scope.stepContext.subWorkflows.length==1) { $scope.showStepDefinitionInBody = false; loadUniqueSubworkflow($scope.stepContext.subWorkflows[0]); } } catch (error) { console.log("error showing workflow step", error); // the ng-repeat seems to swallow and mask any error in the above - can't understand why! but log it here in case something breaks. throw error; } } } function stringify(data) { return JSON.stringify(data, null, 2); }