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 efb5511e1a56059b2755a411e02ce8dc42785962
Author: Alex Heneveld <[email protected]>
AuthorDate: Thu Oct 6 10:42:23 2022 +0100

    improve workflow ui for GC'd tasks, plus other minor ui fixes
    
    create stub tasks where a workflow references a task;
    use improved UI for details on workflow replays;
    show graphic for additional task statuses
---
 .../components/providers/entity-api.provider.js    |   2 +-
 .../components/task-list/task-list.directive.js    |   4 +
 .../components/workflow/workflow-step.directive.js |   2 +-
 .../workflow/workflow-step.template.html           |   2 +-
 .../inspect/activities/activities.controller.js    | 100 +++++++++++++++++++--
 .../inspect/activities/detail/detail.controller.js |  24 ++++-
 .../inspect/activities/detail/detail.template.html |  10 ++-
 ui-modules/utils/status/status.js                  |   8 +-
 8 files changed, 133 insertions(+), 19 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 8ec4e2bd..62d64f76 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
@@ -194,6 +194,6 @@ function EntityApi($http, $q) {
         return $http.get('/v1/applications/'+ applicationId +'/entities/' + 
entityId + '/workflows/', {observable: true, ignoreLoadingBar: true});
     }
     function getWorkflow(applicationId, entityId, workflowId) {
-        return $http.get('/v1/applications/'+ applicationId +'/entities/' + 
entityId + '/workflow/' + workflowId, {observable: true, ignoreLoadingBar: 
true});
+        return $http.get('/v1/applications/'+ applicationId +'/entities/' + 
entityId + '/workflows/' + workflowId, {observable: true, ignoreLoadingBar: 
true});
     }
 }
\ No newline at end of file
diff --git 
a/ui-modules/app-inspector/app/components/task-list/task-list.directive.js 
b/ui-modules/app-inspector/app/components/task-list/task-list.directive.js
index 8f9ac4e3..054aa305 100644
--- a/ui-modules/app-inspector/app/components/task-list/task-list.directive.js
+++ b/ui-modules/app-inspector/app/components/task-list/task-list.directive.js
@@ -206,6 +206,10 @@ export function durationFilter() {
 }
 
 function isTaskWithTag(task, tag) {
+    if (!task.tags) {
+        console.log("Task without tags: ", task);
+        return false;
+    }
     return task.tags.indexOf(tag)>=0;
 }
 
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 e22cf1e2..51e2aef7 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
@@ -113,7 +113,7 @@ export function workflowStepDirective() {
                 $scope.isFocusTask = false;
 
                 if ($scope.task) {
-                    if ($scope.stepContext.taskId === $scope.task.id) {
+                    if (!vm.isNullish($scope.stepContext.taskId) && 
$scope.stepContext.taskId === $scope.task.id) {
                         $scope.isFocusTask = true;
 
                     } else if ($scope.isFocusStep) {
diff --git 
a/ui-modules/app-inspector/app/components/workflow/workflow-step.template.html 
b/ui-modules/app-inspector/app/components/workflow/workflow-step.template.html
index a9d4848b..a0aa233a 100644
--- 
a/ui-modules/app-inspector/app/components/workflow/workflow-step.template.html
+++ 
b/ui-modules/app-inspector/app/components/workflow/workflow-step.template.html
@@ -43,7 +43,7 @@
 
     <div class="workflow-step" id="workflow-step-{{stepIindex}}" 
ng-class="vm.getWorkflowStepClasses(stepIndex)">
         <div class="rhs-icons">
-            <div ng-if="isFocusTask" class="workflow-step-pill focus-step">
+            <div ng-if="isFocusTask" class="workflow-step-pill focus-step" 
title="This step instance is for the task currently selected in the activity 
view.">
                 selected
             </div>
             <div ng-click="vm.toggleExpandState()" class="expand-toggle">
diff --git 
a/ui-modules/app-inspector/app/views/main/inspect/activities/activities.controller.js
 
b/ui-modules/app-inspector/app/views/main/inspect/activities/activities.controller.js
index df4888da..8c3110b7 100644
--- 
a/ui-modules/app-inspector/app/views/main/inspect/activities/activities.controller.js
+++ 
b/ui-modules/app-inspector/app/views/main/inspect/activities/activities.controller.js
@@ -62,17 +62,50 @@ function ActivitiesController($scope, $state, $stateParams, 
$log, $timeout, enti
         onStateChange();
     })
 
+    function mergeActivities() {
+        // merge activitiesRaw records with workflows records, into 
vm.activities;
+        // only once activitiesRaw is loaded
+        if (vm.activitiesRaw) {
+            const newActivitiesMap = {};
+            vm.activitiesRaw.forEach(activity => {
+                newActivitiesMap[activity.id] = activity;
+            });
+
+            // TODO
+            //(vm.workflows || [])
+            Object.values(vm.workflows || {})
+                .forEach(wf => {
+                (wf.replays || []).forEach(wft => {
+                    let newActivity = newActivitiesMap[wtf.taskId];
+                    if (!newActivity) {
+                        // create stub tasks for the replays of workflows
+                        newActivity = makeTaskStubFromWorkflowRecord(wf, wtf);
+                        newActivitiesMap[wtf.taskId] = newActivity;
+                    }
+                    newActivity.workflowId = wtf.workflowId;
+                    newActivity.isWorkflowOldReplay = wtf.workflowId !== 
wtf.taskId;
+                });
+            });
+            newActivitiesMap['extra'] = makeTaskStubMock("Extra workflow", 
"extra", applicationId, entityId);
+
+            vm.activitiesMap = newActivitiesMap;
+            vm.activities = Object.values(vm.activitiesMap);
+        }
+    }
+
     function onStateChange() {
       if ($state.current.name === activitiesState.name && !vm.activities) {
         // only run if we are the active state
         entityApi.entityActivities(applicationId, entityId).then((response) => 
{
-            vm.activities = response.data;
+            vm.activitiesRaw = response.data;
+            mergeActivities();
             observers.push(response.subscribe((response) => {
-                vm.activities = response.data;
+                vm.activitiesRaw = response.data;
+                mergeActivities();
                 vm.error = undefined;
             }));
         }).catch((error) => {
-            $log.warn('Error loading activity for '+activityId, error);
+            $log.warn('Error loading activities for entity '+entityId, error);
             vm.error = 'Cannot load activities for entity with ID: ' + 
entityId;
         });
         
@@ -87,10 +120,22 @@ function ActivitiesController($scope, $state, 
$stateParams, $log, $timeout, enti
             vm.error = 'Cannot load activities (deep) for entity with ID: ' + 
entityId;
         });
 
+        entityApi.getWorkflows(applicationId, entityId).then((response) => {
+          vm.workflows = response.data;
+          mergeActivities();
+          observers.push(response.subscribe((response) => {
+              vm.workflows = response.data;
+              mergeActivities();
+          }));
+        }).catch((error) => {
+          $log.warn('Error loading workflows for entity '+entityId, error);
+        });
+
+
         $scope.$on('$destroy', () => {
-            observers.forEach((observer) => {
-                observer.unsubscribe();
-            });
+          observers.forEach((observer) => {
+              observer.unsubscribe();
+          });
         });
       }
     }
@@ -100,3 +145,46 @@ function ActivitiesController($scope, $state, 
$stateParams, $log, $timeout, enti
         $scope.globalFilters = globalFilters;
     }
 }
+
+export function makeTaskStubFromWorkflowRecord(wf, wft) {
+    return {
+        id: wft.taskId,
+        displayName: wf.name + (wft.reasonForReplay ? " 
("+wft.reasonForReplay+")" : ""),
+        entityId: (wf.entity || {}).id,
+        isError: wtf.isError===false ? false : true,
+        currentStatus: vm.isNullish(wtf.isError) ? "Unavailable" : wtf.status,
+        submitTimeUtc: wft.submittedTimeUtc,
+        startTimeUtc: wft.startTimeUtc,
+        endTimeUtc: wft.endTimeUtc,
+        tags: [
+            "WORKFLOW",
+            {
+                workflowId: wf.workflowId,
+                applicationId: wf.applicationId,
+                entityId: wf.entityId,
+            },
+        ],
+    };
+};
+
+// for testing only
+export function makeTaskStubMock(name, id, applicationId, entityId) {
+    return {
+        id,
+        displayName: name,
+        entityId: entityId,
+        isError: true,
+        currentStatus: "Unavailable",
+        submitTimeUtc: Date.now()-5000,
+        startTimeUtc: Date.now()-4000,
+        endTimeUtc: Date.now()-1000,
+        tags: [
+            "WORKFLOW",
+            {
+                workflowId: 'extra',
+                applicationId: applicationId,
+                entityId: entityId,
+            },
+        ],
+    };
+}
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 9a9e1e0c..648142b9 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
@@ -19,6 +19,7 @@
 import {HIDE_INTERSTITIAL_SPINNER_EVENT} from 
'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
 import template from "./detail.template.html";
 import modalTemplate from './kilt.modal.template.html';
+import {makeTaskStubFromWorkflowRecord, makeTaskStubMock} from 
"../activities.controller";
 
 export const detailState = {
     name: 'main.inspect.activities.detail',
@@ -144,18 +145,33 @@ function DetailController($scope, $state, $stateParams, 
$log, $uibModal, $timeou
             $log.warn('Error loading activity for '+activityId, error);
             // prefer this simpler error message over the specific ones below
             vm.errorBasic = true;
-            vm.error = $sce.trustAsHtml('Cannot load activity with ID: <b>' + 
_.escape(activityId) + '</b> <br/><br/>' +
-                'Task may have completed and been cleared from memory, or may 
not have been run. Details may be available in logs.');
+            vm.error = $sce.trustAsHtml('Cannot load task with ID: <b>' + 
_.escape(activityId) + '</b> <br/><br/>' +
+                'The task is no longer stored in memory. Details may be 
available in logs.');
 
             // in case it corresponds to a workflow and not a task, try 
loading as a workflow
 
             loadWorkflow(null).then(()=> {
+                const wft = (wf.mainTasks || []).find(t => t.taskId === 
activityId);
+                if (wft) {
+                    vm.model.activity = makeTaskStubFromWorkflowRecord(wf, 
wft);
+                    vm.model.workflow.tag = findWorkflowTag(vm.model.activity);
+                } else {
+                    throw "Workflow task "+activityId+" not stored on 
workflow";
+                }
+
                 // give a better error
-                vm.error = $sce.trustAsHtml('Information on workflow <b>' + 
_.escape(activityId) + '</b> is available but with limitations.<br/><br/>' +
-                    'The initial task is no longer available, possibly because 
this workflow has been resumed after a restart.');
+                vm.error = $sce.trustAsHtml('Limited information on workflow 
task <b>' + _.escape(activityId) + '</b>.<br/><br/>' +
+                    (!vm.model.activity.endTimeUtc
+                        ? "The run appears to have been interrupted by a 
server restart or failover."
+                        : 'The workflow is known but this task is no longer 
stored in memory.') );
 
             }).catch(error2 => {
                 $log.debug("ID "+activityId+" does not correspond to workflow 
either", error2);
+
+                // vm.error = $sce.trustAsHtml('Mock data for workflow task 
<b>' + _.escape(activityId) + '</b>.');
+                //
+                // vm.model.activity = makeTaskStubMock("Extra workflow task", 
"extra", applicationId, entityId);
+                // vm.model.workflow.tag = findWorkflowTag(vm.model.activity);
             });
         });
 
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 254435f4..fd5f5fe0 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
@@ -186,16 +186,18 @@
 
                         <div class="workflow-body">
                             <div ng-if="vm.model.workflow.loading == 'loaded'">
-                                <div 
ng-if="vm.model.workflow.data.taskIds.length > 1">
+                                <div 
ng-if="vm.model.workflow.data.replays.length > 1">
                                     <div style="float: right; margin-top: 
-9px;" class="btn-group" uib-dropdown>
                                         <button id="replay-button" 
type="button" class="btn btn-select-dropdown" uib-dropdown-toggle>
                                             Select replay <span 
class="caret"></span>
                                         </button>
                                         <ul class="dropdown-menu" 
uib-dropdown-menu role="menu" aria-labelledby="replay-button">
-                                            <li role="menuitem" ng-repeat="id 
in vm.model.workflow.data.taskIds" id="workflow-replay-{{ id }}">
-                                                <a href="" 
ui-sref="main.inspect.activities.detail({activityId: id})" 
ng-class="{'selected' : vm.model.activityId === id}">
+                                            <li role="menuitem" 
ng-repeat="replay in vm.model.workflow.data.replays" id="workflow-replay-{{ 
replay.taskId }}">
+                                                <a href="" 
ui-sref="main.inspect.activities.detail({activityId: replay.taskId})" 
ng-class="{'selected' : vm.model.activityId === replay.taskId}">
                                                     <i class="fa fa-check 
check"></i>
-                                                    <span class="monospace">{{ 
id }}</span></a> </li>
+<!--                                                    <span 
class="monospace">{{ replay.taskId }}</span>-->
+                                                    {{ replay.submitTimeUtc | 
date : 'MMM dd, yyyy @ H:mm:ss' }} - {{ replay.reasonForReplay || '(no reason 
supplied)' }}
+                                                </a> </li>
                                             <li role="menuitem">
                                                 <a href="" 
ng-click="vm.showReplayHelp()" ng-class="{'selected' : showReplayHelp}"><i>More 
information</i></a>
                                             </li>
diff --git a/ui-modules/utils/status/status.js 
b/ui-modules/utils/status/status.js
index 4677925b..17a21388 100644
--- a/ui-modules/utils/status/status.js
+++ b/ui-modules/utils/status/status.js
@@ -40,9 +40,13 @@ const STATUS = {
     ERROR: {name: 'Error', icon: ICONS.ERROR},
     UNKNOWN: {name: 'Unknown', icon: ICONS.UNKNOWN},
     NO_STATE: {name: '', icon: ICONS.NO_STATE},
+
+    // for tasks
     'In progress': {name: 'In progress', icon: ICONS.STARTING},
     'Completed': {name: 'Completed', icon: ICONS.RUNNING},
-    'Failed': {name: 'Failed', icon: ICONS.ERROR}
+    'Failed': {name: 'Failed', icon: ICONS.ERROR},
+    'Unavailable': {name: 'Incomplete', icon: ICONS.ERROR},
+    'Cancelled': {name: 'Cancelled', icon: ICONS.ERROR},
 };
 
 const MODULE_NAME = 'brooklyn.components.status';
@@ -77,7 +81,7 @@ export function statusIconDirective() {
 }
 export function statusTextDirective() {
     var directive = {
-        template: '<div ng-class="statusClass()">{{status.name}}</div>',
+        template: '<div ng-class="statusClass()">{{status.name || 
value}}</div>',
         restrict: 'E',
         scope: {
             value: '@'

Reply via email to