This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-ui.git
commit 62c2b8668eb6910a8a08c1a886fc8bf8d1020313 Author: Alex Heneveld <[email protected]> AuthorDate: Mon Oct 10 21:26:43 2022 +0100 replay buttons and arrow tidies --- .../components/providers/entity-api.provider.js | 5 ++ .../components/workflow/workflow-step.directive.js | 2 +- .../workflow/workflow-steps.directive.js | 20 ++++-- .../inspect/activities/detail/detail.controller.js | 72 ++++++++++++++++------ .../inspect/activities/detail/detail.template.html | 14 ++--- 5 files changed, 83 insertions(+), 30 deletions(-) diff --git a/ui-modules/app-inspector/app/components/providers/entity-api.provider.js b/ui-modules/app-inspector/app/components/providers/entity-api.provider.js index 62d64f76..aa34cfd3 100644 --- a/ui-modules/app-inspector/app/components/providers/entity-api.provider.js +++ b/ui-modules/app-inspector/app/components/providers/entity-api.provider.js @@ -74,6 +74,7 @@ function EntityApi($http, $q) { getWorkflows: getWorkflows, getWorkflow: getWorkflow, + replayWorkflow: replayWorkflow, }; function getEntity(applicationId, entityId) { @@ -196,4 +197,8 @@ function EntityApi($http, $q) { function getWorkflow(applicationId, entityId, workflowId) { return $http.get('/v1/applications/'+ applicationId +'/entities/' + entityId + '/workflows/' + workflowId, {observable: true, ignoreLoadingBar: true}); } + function replayWorkflow(applicationId, entityId, workflowId, step, options) { + return $http.post('/v1/applications/'+ applicationId +'/entities/' + entityId + '/workflows/' + workflowId + + '/replay/from/' + step, {params: options, observable: true, ignoreLoadingBar: true}); + } } \ No newline at end of file diff --git a/ui-modules/app-inspector/app/components/workflow/workflow-step.directive.js b/ui-modules/app-inspector/app/components/workflow/workflow-step.directive.js index dadf25a7..7cc22544 100644 --- a/ui-modules/app-inspector/app/components/workflow/workflow-step.directive.js +++ b/ui-modules/app-inspector/app/components/workflow/workflow-step.directive.js @@ -118,7 +118,7 @@ export function workflowStepDirective() { : ($scope.isWorkflowError && $scope.isCurrentMaybeInactive) ? 'The workflow encountered an error around this step.' : null; const incomplete = $scope.osi.countStarted - $scope.osi.countCompleted > ($scope.isCurrentAndActive ? 1 : 0); - $scope.stepCurrentWarning = incomplete ? 'This step has previously been interrupted.' : null; + $scope.stepCurrentWarning = incomplete && !$scope.stepCurrentError ? 'This step has previously had an error' : null; $scope.stepCurrentSuccess = (!$scope.isCurrentAndActive && !incomplete && $scope.osi.countCompleted > 0) ? 'This step has completed without errors.' : null; diff --git a/ui-modules/app-inspector/app/components/workflow/workflow-steps.directive.js b/ui-modules/app-inspector/app/components/workflow/workflow-steps.directive.js index c808f1f8..143b665c 100644 --- a/ui-modules/app-inspector/app/components/workflow/workflow-steps.directive.js +++ b/ui-modules/app-inspector/app/components/workflow/workflow-steps.directive.js @@ -89,6 +89,7 @@ function makeArrows(workflow, steps) { defs.push('<marker id="arrowhead" markerWidth="'+3*arrowheadWidth+'" markerHeight="'+3*arrowheadWidth+'" refX="'+0+'" refY="'+1.5*arrowheadWidth+'" orient="auto"><polygon fill="#000" points="0 0, '+3*arrowheadWidth+' '+1.5*arrowheadWidth+', 0 '+(3*arrowheadWidth)+'" /></marker><'); defs.push('<marker id="arrowhead-gray" markerWidth="'+3*arrowheadWidth+'" markerHeight="'+3*arrowheadWidth+'" refX="'+0+'" refY="'+1.5*arrowheadWidth+'" orient="auto"><polygon fill="#C0C0C0" points="0 0, '+3*arrowheadWidth+' '+1.5*arrowheadWidth+', 0 '+(3*arrowheadWidth)+'" /></marker><'); + defs.push('<marker id="arrowhead-red" markerWidth="'+3*arrowheadWidth+'" markerHeight="'+3*arrowheadWidth+'" refX="'+0+'" refY="'+1.5*arrowheadWidth+'" orient="auto"><polygon fill="red" points="0 0, '+3*arrowheadWidth+' '+1.5*arrowheadWidth+', 0 '+(3*arrowheadWidth)+'" /></marker><'); if (steps) { let gradientCount = 0; @@ -179,6 +180,8 @@ function makeArrows(workflow, steps) { return arrowSvg(s1, s2, opts); } + let jumpSizes = {1: true}; + function arrowStep2(prev, i, opts) { let curveX = 0.5; let curveY = 0.75; @@ -216,7 +219,6 @@ function makeArrows(workflow, steps) { return 'rgb('+gray+','+gray+','+gray+')'; } - let jumpSizes = {1: true}; let arrowSpecs = {}; function recordTransition(from, to, opts) { if (to!=-1 && from!=-1 && to!=from) { @@ -259,22 +261,32 @@ function makeArrows(workflow, steps) { for (var i = 0; i < steps.length; i++) { const s = workflow.data.stepsDefinition[i]; + + let opts = { insertionPoint: 0 }; + if (workflow.data.currentStepIndex === i && workflow.data.status && workflow.data.status.startsWith('ERROR')) { + recordTransition(i, -1, { ...opts, color: 'red', arrowheadId: 'arrowhead-red' }); + } + + opts = { ...opts, color: '#C0C0C0', arrowheadId: 'arrowhead-gray', dashLength: 8 }; + let next = null; if (s.next) { if (s.next.toLowerCase()=='end') next = -1; else if (indexOfId[s.next]) next = indexOfId[s.next]; } if (isStepType(s, 'return')) next = -1; + if (next!=null) { // special next per step - recordTransition(i, next, { insertionPoint: 0, color: '#C0C0C0', arrowheadId: 'arrowhead-gray', dashLength: 8 }); + recordTransition(i, next, opts); if (!s.condition) continue; } // if nothing special, or if was conditional, then go to next step + // (only go forward 1, even if it is conditional, otherwise too many arrows) + next = i+1; if (i + 1 >= steps.length) next = -1; - - recordTransition(i, next, { insertionPoint: 0, color: '#C0C0C0', arrowheadId: 'arrowhead-gray', dashLength: 8 }); + recordTransition(i, next, opts); } jumpSizes = Object.keys(jumpSizes).sort(); diff --git a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.controller.js b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.controller.js index de1fa975..a5cbe6b2 100644 --- a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.controller.js +++ b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.controller.js @@ -51,7 +51,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou vm.modalTemplate = modalTemplate; vm.wideKilt = false; - vm.actions = {}; + $scope.actions = {}; let observers = []; @@ -76,21 +76,57 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou vm.model.workflow.applicationId = workflowTag.applicationId; vm.model.workflow.entityId = workflowTag.entityId; - vm.actions.workflowReplays = []; + $scope.actions.workflowReplays = []; if (!vm.model.activity.endTimeUtc || vm.model.activity.endTimeUtc<=0) { // can't replay if active (same logic as 'cancel') } else { - [ - // TODO get from server - // [ 'step 3 (continuing)', null ], - // [ 'step 3 (replay point)', [2] ], - // [ 'start (replay point)', [0] ], - ].forEach(r => vm.actions.workflowReplays.push(r)); - vm.actions.workflowReplays.forEach(r => { - r.push( () => console.log("TODO - replay from "+r[0], r[1]) ); - }) + $scope.actions.workflowReplays = []; + const stepIndex = (vm.model.workflow.tag || {}).stepIndex; + + let replayableFromStart = vm.model.workflow.data.replayableFromStart, replayableContinuing = vm.model.workflow.data.replayableLastStep>=0; + + if (replayableContinuing) { + $scope.actions.workflowReplays.push({ targetId: 'end', targetName: 'Resume '+(stepIndex>=0 ? 'workflow ' : '')+' (at step '+(vm.model.workflow.data.replayableLastStep+1)+')' }); + } + + // get current step, replay from that step + if (stepIndex>=0) { + const osi = workflow.data.oldStepInfo[stepIndex] || {}; + if (osi.replayableFromHere) { + $scope.actions.workflowReplays.push({ targetId: ''+stepIndex, targetName: 'Replay from here (step '+(stepIndex+1) }); + } else { + $scope.actions.workflowReplays.push({ targetId: ''+stepIndex, targetName: 'Force replay from here (step '+(stepIndex+1), force: true }); + } + } + + if (replayableFromStart) { + let w1 = 'Restart', w2 = '(not resumable)'; + if (stepIndex<0) { w1 = 'Run'; w2 = 'again'; } + else if (_.isNil(stepIndex)) { w2 = '(did not start)'; } + else if (replayableContinuing) w2 = ''; + + $scope.actions.workflowReplays.push({targetId: 'start', targetName: 'Restart '+(stepIndex>=0 ? 'workflow ' : '')+reason}); + } + + if (!replayableFromStart) { + $scope.actions.workflowReplays.push({targetId: 'start', targetName: 'Force restart', force: true}); + } + // force replays + $scope.actions.workflowReplays.forEach(r => { + // could prompt for a reason + const targetId = r.targetId; + const opts = {}; + opts.reason = "UI manual replay"; + if (r.force) { + opts.force = true; + opts.reason += " (forced)"; + } + r.action = () => { + entityApi.replay(applicationId, entityId, $scope.workflowId. targetId, opts); + }; + }); } - if (!vm.actions.workflowReplays.length) delete vm.actions['workflowReplays']; + if (!$scope.actions.workflowReplays.length) delete $scope.actions['workflowReplays']; if (vm.model.workflow.data.status === 'RUNNING') wResponse.interval(1000); observers.push(wResponse.subscribe((wResponse2)=> { @@ -112,22 +148,22 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou activityApi.activity(activityId).then((response)=> { vm.model.activity = response.data; - delete vm.actions['effector']; - delete vm.actions['invokeAgain']; + delete $scope.actions['effector']; + delete $scope.actions['invokeAgain']; if ((vm.model.activity.tags || []).find(t => t=="EFFECTOR")) { const effectorName = (vm.model.activity.tags.find(t => t.effectorName) || {}).effectorName; const effectorParams = (vm.model.activity.tags.find(t => t.effectorParams) || {}).effectorParams; if (effectorName) { - vm.actions.effector = {effectorName}; + $scope.actions.effector = {effectorName}; if (effectorParams) { - vm.actions.invokeAgain = {effectorName, effectorParams, doAction: () => vm.invokeEffector(effectorName, effectorParams) }; + $scope.actions.invokeAgain = {effectorName, effectorParams, doAction: () => vm.invokeEffector(effectorName, effectorParams) }; } } } - delete vm.actions['cancel']; + delete $scope.actions['cancel']; if (!vm.model.activity.endTimeUtc || vm.model.activity.endTimeUtc<=0) { - vm.actions.cancel = { doAction: () => { activityApi.cancelActivity(activityId); } }; + $scope.actions.cancel = { doAction: () => { activityApi.cancelActivity(activityId); } }; } $scope.workflowId = null; // if the task loads, force the workflow id to be found on it, otherwise ignore it diff --git a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.template.html b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.template.html index 1efc6860..08b4b889 100644 --- a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.template.html +++ b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.template.html @@ -45,27 +45,27 @@ <code><a ui-sref="main.inspect.activities.detail({entityId: vm.model.activity.blockingTask.metadata.entityId, activityId: vm.model.activity.blockingTask.metadata.id})">{{vm.model.activity.blockingTask.metadata.taskName}}</a></code> for <strong><a ui-sref="main.inspect.summary({entityId: vm.model.activity.blockingTask.metadata.entityId})">{{vm.model.activity.blockingTask.metadata.entityDisplayName}} entity</a></strong> </div> - <div style="float:right;" ng-if="vm.isNonEmpty(vm.actions)" class="btn-group dropdown-nested dropdown-menu-right" uib-dropdown-nested dropdown-append-to-body="true"> + <div style="float:right;" ng-if="vm.isNonEmpty(actions)" class="btn-group dropdown-nested dropdown-menu-right" uib-dropdown-nested dropdown-append-to-body="true"> <br-button uib-dropdown-toggle-nested type="btn-primary"> Actions <span class="caret"></span> </br-button> <ul uib-dropdown-menu-nested class="dropdown-at-root dropdown-menu-right"> - <li><a href="" ng-if="vm.actions.cancel" ng-click="vm.actions.cancel.doAction()">Cancel</a></li> + <li><a href="" ng-if="actions.cancel" ng-click="actions.cancel.doAction()">Cancel</a></li> - <li ng-if="vm.actions.workflowReplays" uib-dropdown-nested dropdown-append-to-body="true"> + <li ng-if="actions.workflowReplays" uib-dropdown-nested dropdown-append-to-body="true"> <a href="" uib-dropdown-toggle-nested>Replay workflow <span class="caret"></span></a> <ul class="dropdown-submenu-left dropdown-at-root dropdown-menu-right" uib-dropdown-menu-nested> - <li ng-repeat="replay in vm.actions.workflowReplays track by $index" id="replay {{ replay[0] }}"> - <a class="dropdown-item" href="" ng-click="replay[2]()">From {{ replay[0] }}</a> + <li ng-repeat="replay in actions.workflowReplays track by $index" id="replay-{{replay.targetId}}"> + <a class="dropdown-item" href="" ng-click="replay.action()">{{ replay.targetName }}</a> </li> </ul> </li> - <li><a href="" ng-if="vm.actions.invokeAgain" ng-click="vm.actions.invokeAgain.doAction()">Reinvoke effector</a></li> - <li><a href="" ng-if="vm.actions.effector" ui-sref="main.inspect.effectors({search: vm.actions.effector.effectorName})">Open in effector tab</a></li> + <li><a href="" ng-if="actions.invokeAgain" ng-click="actions.invokeAgain.doAction()">Reinvoke effector</a></li> + <li><a href="" ng-if="actions.effector" ui-sref="main.inspect.effectors({search: actions.effector.effectorName})">Open in effector tab</a></li> </ul> </div>
