You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2022/10/21 13:58:26 UTC

[brooklyn-ui] 17/24: replay buttons and arrow tidies

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 <al...@cloudsoft.io>
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>