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:18 UTC

[brooklyn-ui] 09/24: tidy workflow consistency (when tasks don't load), date, misc

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 e86f96c6b1583b166947b71147fd9a84ce8020e6
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Thu Oct 6 14:23:06 2022 +0100

    tidy workflow consistency (when tasks don't load), date, misc
---
 .../components/task-list/task-list.directive.js    | 27 ++++++++++++++++
 .../components/task-list/task-list.template.html   |  8 ++---
 .../components/workflow/workflow-step.directive.js |  6 ++--
 .../workflow/workflow-step.template.html           | 32 +++++++++----------
 .../inspect/activities/activities.controller.js    | 18 +++++------
 .../inspect/activities/detail/detail.controller.js | 28 +++++++++-------
 .../main/inspect/activities/detail/detail.less     |  5 +++
 .../inspect/activities/detail/detail.template.html | 37 ++++++++++++++--------
 .../inspect/management/detail/detail.template.html |  2 +-
 .../home/app/views/about/about.controller.js       |  2 ++
 10 files changed, 108 insertions(+), 57 deletions(-)

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 054aa305..35fc8b03 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
@@ -18,13 +18,16 @@
  */
 import angular from "angular";
 import {fromNow, duration} from "brooklyn-ui-utils/utils/momentp";
+import moment from "moment";
 import template from "./task-list.template.html";
+import {getTaskWorkflowTag} from "../../views/main/inspect/activities/detail/detail.controller";
 
 const MODULE_NAME = 'inspector.task-list';
 
 angular.module(MODULE_NAME, [])
     .directive('taskList', taskListDirective)
     .filter('timeAgoFilter', timeAgoFilter)
+    .filter('dateFilter', dateFilter)
     .filter('durationFilter', durationFilter)
     .filter('activityTagFilter', activityTagFilter)
     .filter('activityFilter', ['$filter', activityFilter]);
@@ -85,6 +88,11 @@ export function taskListDirective() {
         $scope.$watch('model.filterResult', function () {
             if ($scope.filteredCallback && $scope.model.filterResult) $scope.filteredCallback()( $scope.model.filterResult, $scope.globalFilters );
         });
+        $scope.getTaskWorkflowId = task => {
+            const tag = getTaskWorkflowTag(task);
+            if (tag) return tag.workflowId;
+            return null;
+        };
     }
 
     function tagReducer(result, tag) {
@@ -192,6 +200,7 @@ function topLevelTasks(tasks) {
 
 export function timeAgoFilter() {
     function timeAgo(input) {
+        if (!input || input<=0) return "-";
         return fromNow(input);
     }
 
@@ -199,6 +208,24 @@ export function timeAgoFilter() {
 
     return timeAgo;
 }
+
+export function dateFilter() {
+    function date(input, args) {
+        // if (!input || input<=0) return "-";
+
+        if (args==='short') {
+            return moment(input).format('MMM D, yyyy @ HH:mm:ss');
+        } else if (args==='iso') {
+            return moment(input).format('yyyy-MM-DD HH:mm:ss.SSS');
+        } else {
+            return moment(input).format('MMM D, yyyy @ HH:mm:ss.SSS');
+        }
+        return "TODO - "+input;
+    }
+
+    return date;
+}
+
 export function durationFilter() {
     return function (input) {
         return duration(input);
diff --git a/ui-modules/app-inspector/app/components/task-list/task-list.template.html b/ui-modules/app-inspector/app/components/task-list/task-list.template.html
index a853d64f..2db6f8ba 100644
--- a/ui-modules/app-inspector/app/components/task-list/task-list.template.html
+++ b/ui-modules/app-inspector/app/components/task-list/task-list.template.html
@@ -57,22 +57,22 @@
             <tbody>
             <tr ng-repeat="task in tasks | activityTagFilter : [model.filterByTag, globalFilters] | activityFilter:filterValue as filterResult track by task.id">
                 <td class="status">
-                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id})">
+                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id, workflowId: getTaskWorkflowId(task)})">
                     <brooklyn-status-icon value="{{task.currentStatus}}" ng-if="!isScheduled(task)"></brooklyn-status-icon>
                     <span ng-if="isScheduled(task)" class="custom-status-task-icon"><i class="fa fa-clock-o" style="font-size: 250%;"></i></span>
                     </a>
                 </td>
                 <td class="name">
-                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id})">{{task.displayName}}
+                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id, workflowId: getTaskWorkflowId(task)})">{{task.displayName}}
                     </a>
                 </td>
                 <td class="started">
-                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id})">
+                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id, workflowId: getTaskWorkflowId(task)})">
                         {{task.startTimeUtc | timeAgoFilter}}
                     </a>
                 </td>
                 <td class="duration">
-                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id})"
+                    <a ui-sref="main.inspect.activities.detail({entityId: task.entityId, activityId: task.id, workflowId: getTaskWorkflowId(task)})"
                         	ng-if="task.startTimeUtc">
                         {{getTaskDuration(task) | durationFilter}} <span ng-if="task.endTimeUtc === null">and counting</span>
                     </a>
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 5ef1dde2..30b8b8cf 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
@@ -51,6 +51,7 @@ export function workflowStepDirective() {
 
             let step = $scope.step;
             let index = $scope.stepIndex;
+            $scope.workflowId = ($scope.workflow && $scope.workflow.data || {}).workflowId;
 
             vm.stepDetails = () => stepDetails($sce, $scope.workflow, step, index, $scope.expanded);
             vm.toggleExpandState = () => {
@@ -98,11 +99,12 @@ export function workflowStepDirective() {
                 $scope.workflowStepClasses = [];
                 if (workflow.data.currentStepIndex === index) $scope.workflowStepClasses.push('current-step');
 
-                $scope.isCurrent = (workflow.data.currentStepIndex === index);
                 $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.isCurrent ? workflow.data.currentStepInstance : $scope.osi.context) || {};
+                $scope.stepContext = ($scope.isCurrentMaybeInactive ? workflow.data.currentStepInstance : $scope.osi.context) || {};
                 $scope.isFocusStep = $scope.workflow.tag && ($scope.workflow.tag.stepIndex === index);
                 $scope.isFocusTask = false;
 
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 86a79342..5f0b78b4 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
@@ -19,7 +19,7 @@
 <div class="workflow-step-outer">
 
     <div class="workflow-step-status-indicators">
-        <span ng-if="isCurrent">
+        <span ng-if="isCurrentAndActive">
             <span ng-if="isRunning" class="running-status">
                 <brooklyn-status-icon value="STARTING"></brooklyn-status-icon>
             </span>
@@ -31,7 +31,7 @@
             </span>
 <!--            <span ng-if="osi.countCompleted > 1">{{ osi.countCompleted }}</span>-->
         </span>
-        <span ng-if="osi.countStarted && osi.countStarted != osi.countCompleted && !(isCurrent && isRunning)">
+        <span ng-if="osi.countStarted && osi.countStarted != osi.countCompleted && !isCurrentAndActive">
             <span class="color-failed" ng-if="isWorkflowError">
                 <i class="fa fa-times-circle"></i>
             </span>
@@ -86,7 +86,7 @@
                             </span>
                         </span>
                         <span ng-if="osi.countCompleted != osi.countStarted">
-                            <span ng-if="isCurrent">
+                            <span ng-if="isCurrentAndActive">
                                 <span ng-if="osi.countCompleted == osi.countStarted - 1">
                                     This step is currently running
                                 </span>
@@ -94,24 +94,24 @@
                                     This step has had errors previously and is currently running
                                 </span>
                             </span>
-                            <span ng-if="!isCurrent">
+                            <span ng-if="!isCurrentAndActive">
                                 <span ng-if="osi.countStarted == 1">
                                     This step had errors when it ran
                                 </span>
-                                <span ng-if="osi.countStarted > 2 && osi.countCompleted==0">
-                                    This step has had errors on all previous runs, including when last run
+                                <span ng-if="osi.countStarted >= 2 && osi.countCompleted==0">
+                                    This step has had errors on all previous runs, including the last run,
                                 </span>
-                                <span ng-if="osi.countStarted > 2 && osi.countCompleted>0">
+                                <span ng-if="osi.countStarted >= 2 && osi.countCompleted>0">
                                     This step has had errors on some previous runs. It most recently ran
                                 </span>
                             </span>
                         </span>
 
                         <span ng-if="isFocusTask">
-                            as the activity currently being viewed (<span class="monospace">{{ stepContext.taskId }}</span>).
+                            in the task focused on in this page (<span class="monospace">{{ stepContext.taskId }}</span>).
                         </span>
                         <span ng-if="!isFocusTask">
-                            in <a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: stepContext.taskId })">task <span class="monospace">{{ stepContext.taskId }}</span></a>.
+                            in <a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: stepContext.taskId, workflowId })">task <span class="monospace">{{ stepContext.taskId }}</span></a>.
                         </span>
                     </span>
                 </div>
@@ -130,18 +130,18 @@
                 <div ng-if="osi.countStarted > 1 && osi.countStarted > osi.countCompleted" class="space-above">
                     <div class="data-row"><div class="A">Runs</div> <div class="B"><b>{{ osi.countStarted }}</b></div></div>
                     <div class="data-row"><div class="A">Succeeded</div> <div class="B">{{ osi.countCompleted }}</div></div>
-                    <div class="data-row"><div class="A">Failed</div> <div class="B">{{ osi.countCompleted - osi.countStarted - (isCurrent ? 1 : 0) }}</div></div>
+                    <div class="data-row"><div class="A">Failed</div> <div class="B">{{ osi.countStarted - osi.countCompleted - (isCurrentAndActive ? 1 : 0) }}</div></div>
                 </div>
 
                 <div class="more-space-above" ng-if="stepContext.taskId">
                     <div class="data-row">
-                        <div class="A"><span ng-if="isCurrent">CURRENT</span><span ng-if="!isCurrent">LAST</span> EXECUTION</div>
+                        <div class="A"><span ng-if="isCurrentAndActive">CURRENT</span><span ng-if="!isCurrent">LAST</span> EXECUTION</div>
                         <div class="B">
                                     <span ng-if="isFocusTask">
                                         task <span class="monospace">{{ stepContext.taskId }}</span>
                                     </span>
                             <span ng-if="!isFocusTask">
-                                         <a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: stepContext.taskId })"
+                                         <a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: stepContext.taskId, workflowId })"
                                             >task <span class="monospace">{{ stepContext.taskId }}</span></a>
                                     </span>
                         </div>
@@ -150,16 +150,16 @@
                         <div class="data-row nested"><div class="A">Preceeded by</div> <div class="B">
                             <span ng-if="osi.previousTaskId">
                                 Step {{ osi.previous[0]+1 }}
-                                (<a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: osi.previousTaskId })"
+                                (<a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: osi.previousTaskId, workflowId })"
                                     >task <span class="monospace">{{ osi.previousTaskId }}</span></a>)
                             </span>
                             <span ng-if="!osi.previousTaskId">(workflow start)</span>
                         </div></div>
 
-                        <div class="data-row nested" ng-if="!isCurrent"><div class="A">Followed by</div> <div class="B">
+                        <div class="data-row nested" ng-if="!isCurrentMaybeInactive"><div class="A">Followed by</div> <div class="B">
                             <span ng-if="osi.nextTaskId">
                                 Step {{ osi.next[0]+1 }}
-                                (<a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: osi.nextTaskId })"
+                                (<a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: osi.nextTaskId, workflowId })"
                                     >task <span class="monospace">{{ osi.nextTaskId }}</span></a>)
                             </span>
                             <span ng-if="!osi.nextTaskId">(workflow end)</span>
@@ -184,7 +184,7 @@
                         <div class="data-row nested" ng-if="stepContext.otherMetadata" ng-repeat="(key,value) in stepContext.otherMetadata" id="$key">
                             <div class="A">{{ key }}</div> <div class="B multiline-code">{{ vm.yamlOrPrimitive(value) }}</div>
                         </div>
-                        <div class="data-row nested" ng-if="!isCurrent && stepContext.output"><div class="A">Output</div> <div class="B multiline-code">{{ vm.yaml(stepContext.output) }}</div></div>
+                        <div class="data-row nested" ng-if="!isCurrentMaybeInactive && stepContext.output"><div class="A">Output</div> <div class="B multiline-code">{{ vm.yaml(stepContext.output) }}</div></div>
                     </div>
                 </div>
 
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 8c3110b7..39f0622b 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
@@ -76,14 +76,14 @@ function ActivitiesController($scope, $state, $stateParams, $log, $timeout, enti
             Object.values(vm.workflows || {})
                 .forEach(wf => {
                 (wf.replays || []).forEach(wft => {
-                    let newActivity = newActivitiesMap[wtf.taskId];
+                    let newActivity = newActivitiesMap[wft.taskId];
                     if (!newActivity) {
                         // create stub tasks for the replays of workflows
-                        newActivity = makeTaskStubFromWorkflowRecord(wf, wtf);
-                        newActivitiesMap[wtf.taskId] = newActivity;
+                        newActivity = makeTaskStubFromWorkflowRecord(wf, wft);
+                        newActivitiesMap[wft.taskId] = newActivity;
                     }
-                    newActivity.workflowId = wtf.workflowId;
-                    newActivity.isWorkflowOldReplay = wtf.workflowId !== wtf.taskId;
+                    newActivity.workflowId = wft.workflowId;
+                    newActivity.isWorkflowOldReplay = wft.workflowId !== wft.taskId;
                 });
             });
             newActivitiesMap['extra'] = makeTaskStubMock("Extra workflow", "extra", applicationId, entityId);
@@ -116,7 +116,7 @@ function ActivitiesController($scope, $state, $stateParams, $log, $timeout, enti
                 vm.error = undefined;
             }));
         }).catch((error) => {
-            $log.warn('Error loading activity children deep for '+activityId, error);
+            $log.warn('Error loading activity children deep for entity '+entityId, error);
             vm.error = 'Cannot load activities (deep) for entity with ID: ' + entityId;
         });
 
@@ -151,9 +151,9 @@ export function makeTaskStubFromWorkflowRecord(wf, wft) {
         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,
+        isError: wft.isError===false ? false : true,
+        currentStatus: _.isNil(wft.isError) ? "Unavailable" : wft.status,
+        submitTimeUtc: wft.submitTimeUtc,
         startTimeUtc: wft.startTimeUtc,
         endTimeUtc: wft.endTimeUtc,
         tags: [
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 648142b9..b8dfc022 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
@@ -23,7 +23,7 @@ import {makeTaskStubFromWorkflowRecord, makeTaskStubMock} from "../activities.co
 
 export const detailState = {
     name: 'main.inspect.activities.detail',
-    url: '/:activityId',
+    url: '/:activityId?workflowId',
     template: template,
     controller: ['$scope', '$state', '$stateParams', '$log', '$uibModal', '$timeout', '$sanitize', '$sce', 'activityApi', 'entityApi', 'brUtilsGeneral', DetailController],
     controllerAs: 'vm',
@@ -34,8 +34,9 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
     const {
         applicationId,
         entityId,
-        activityId
+        activityId,
     } = $stateParams;
+    $scope.workflowId = $stateParams.workflowId;
 
     let vm = this;
     vm.model = {
@@ -50,6 +51,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
 
     vm.modalTemplate = modalTemplate;
     vm.wideKilt = false;
+    vm.actions = {};
 
     let observers = [];
 
@@ -62,8 +64,10 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
             }
             vm.model.workflow.loading = 'loading';
 
-            return entityApi.getWorkflow(workflowTag.applicationId || applicationId, workflowTag.entityId || entityId, workflowTag.workflowId || activityId).then(wResponse => {
-                workflowTag = {applicationId, entityId, workflowId: activityId, ...workflowTag};
+            $scope.workflowId = workflowTag.workflowId || $scope.workflowId || activityId;
+            return entityApi.getWorkflow(workflowTag.applicationId || applicationId, workflowTag.entityId || entityId, $scope.workflowId).then(wResponse => {
+                $scope.workflowId = wResponse.data.workflowId;
+                workflowTag = {applicationId, entityId, workflowId: $scope.workflowId, ...workflowTag};
                 if (optimistic) {
                     vm.model.workflow.tag = workflowTag;
                 }
@@ -99,7 +103,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
                     throw error;
                 }
 
-                console.log("ERROR loading workflow " + workflowTag.workflowId, error);
+                console.log("ERROR loading workflow " + $scope.workflowId, error);
                 vm.model.workflow.loading = 'error';
             });
         };
@@ -107,7 +111,6 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
         activityApi.activity(activityId).then((response)=> {
             vm.model.activity = response.data;
 
-            vm.actions = vm.actions || {};
             delete vm.actions['effector'];
             delete vm.actions['invokeAgain'];
             if ((vm.model.activity.tags || []).find(t => t=="EFFECTOR")) {
@@ -126,8 +129,9 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
                 vm.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
             if ((vm.model.activity.tags || []).find(t => t=="WORKFLOW")) {
-                const workflowTag = findWorkflowTag(vm.model.activity);
+                const workflowTag = getTaskWorkflowTag(vm.model.activity);
                 if (workflowTag) {
                     vm.model.workflow.tag = workflowTag;
                     loadWorkflow(workflowTag);
@@ -151,10 +155,10 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
             // 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);
+                const wft = (vm.model.workflow.data.replays || []).find(t => t.taskId === activityId);
                 if (wft) {
-                    vm.model.activity = makeTaskStubFromWorkflowRecord(wf, wft);
-                    vm.model.workflow.tag = findWorkflowTag(vm.model.activity);
+                    vm.model.activity = makeTaskStubFromWorkflowRecord(vm.model.workflow.data, wft);
+                    vm.model.workflow.tag = getTaskWorkflowTag(vm.model.activity);
                 } else {
                     throw "Workflow task "+activityId+" not stored on workflow";
                 }
@@ -251,7 +255,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
             $state.go('main.inspect.activities.detail', {
                 applicationId: applicationId,
                 entityId: entityId,
-                activityId: response.data.id
+                activityId: response.data.id,
             });
         });
     }
@@ -285,7 +289,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
     vm.isNonEmpty = x => !vm.isEmpty(x);
 }
 
-function findWorkflowTag(task) {
+export function getTaskWorkflowTag(task) {
     if (!task) return null;
     if (!task.tags) return null;
     return task.tags.find(t => t.workflowId);
diff --git a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.less b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.less
index 63068c45..38eb4b34 100644
--- a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.less
+++ b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/detail.less
@@ -228,3 +228,8 @@
         margin-right: 36px;
     }
 }
+
+.dropdown-menu-replays {
+    width: auto;
+    max-width: 32em;
+}
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 fd5f5fe0..c96f9b75 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
@@ -122,7 +122,7 @@
                                             {{vm.model.activity.submitTimeUtc | timeAgoFilter}}
                                         </div>
                                         <div class="utcTime fade" ng-show="showUTC">
-                                            {{vm.model.activity.submitTimeUtc | date : 'MMM dd, yyyy @ H:mm:ss.sss'}}
+                                            {{vm.model.activity.submitTimeUtc | dateFilter }}
                                         </div>
                                     </div>
                                 </div>
@@ -133,7 +133,7 @@
                                             {{vm.model.activity.startTimeUtc | timeAgoFilter}}
                                         </div>
                                         <div class="utcTime fade" ng-show="showUTC">
-                                            {{vm.model.activity.startTimeUtc | date : 'MMM dd, yyyy @ H:mm:ss.sss'}}
+                                            {{vm.model.activity.startTimeUtc | dateFilter }}
                                         </div>
                                     </div>
                                 </div>
@@ -144,7 +144,7 @@
                                             {{vm.model.activity.endTimeUtc | timeAgoFilter}}
                                         </div>
                                         <div class="utcTime fade" ng-show="showUTC">
-                                            {{vm.model.activity.endTimeUtc | date : 'MMM dd, yyyy @ H:mm:ss.sss'}}
+                                            {{vm.model.activity.endTimeUtc | dateFilter }}
                                         </div>
                                     </div>
                                 </div>
@@ -191,12 +191,15 @@
                                         <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">
+                                        <ul class="dropdown-menu dropdown-menu-right dropdown-menu-replays" uib-dropdown-menu role="menu" aria-labelledby="replay-button">
                                             <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}">
+                                                <a href="" ui-sref="main.inspect.activities.detail({activityId: replay.taskId, workflowId: workflowId})" ng-class="{'selected' : vm.model.activityId === replay.taskId}">
                                                     <i class="fa fa-check check"></i>
 <!--                                                    <span class="monospace">{{ replay.taskId }}</span>-->
-                                                    {{ replay.submitTimeUtc | date : 'MMM dd, yyyy @ H:mm:ss' }} - {{ replay.reasonForReplay || '(no reason supplied)' }}
+                                                    <span ng-if="replay.reasonForReplay">{{ replay.reasonForReplay }} (</span
+                                                    ><span>{{ replay.submitTimeUtc | dateFilter: 'short' }}</span
+                                                    ><span ng-if="replay.reasonForReplay">)</span>
+
                                                 </a> </li>
                                             <li role="menuitem">
                                                 <a href="" ng-click="vm.showReplayHelp()" ng-class="{'selected' : showReplayHelp}"><i>More information</i></a>
@@ -205,14 +208,20 @@
                                     </div>
                                 </div>
                                 <div style="margin-top: 12px; margin-bottom: 24px;">
-                                    This task is for
-                                    <span ng-if="!vm.isNullish(vm.model.workflow.tag.stepIndex)">step <b>{{ vm.model.workflow.tag.stepIndex+1 }}</b>
+                                    This task is
+                                    <span ng-if="!vm.isNullish(vm.model.workflow.tag.stepIndex)">for step <b>{{ vm.model.workflow.tag.stepIndex+1 }}</b>
                                         in
-                                        <a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.taskId})">
+                                        <a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.taskId, workflowId})">
                                             workflow <span class="monospace">{{vm.model.workflow.data.workflowId}}</span>:
                                             <b>{{vm.model.workflow.data.name}}</b></a>.
                                     </span>
-                                    <span ng-if="vm.isNullish(vm.model.workflow.tag.stepIndex)">
+                                    <span ng-if="vm.isNullish(vm.model.workflow.tag.stepIndex) && !vm.model.activity.id">
+                                        part of
+                                        workflow <span class="monospace">{{vm.model.workflow.data.workflowId}}</span>:
+                                        <b>{{vm.model.workflow.data.name}}</b>.
+                                    </span>
+                                    <span ng-if="vm.isNullish(vm.model.workflow.tag.stepIndex) && vm.model.activity.id">
+                                        for
                                         <span ng-if="vm.model.workflow.data.taskIds.length>1">
                                             <span ng-if="vm.model.workflow.data.taskIds[vm.model.workflow.data.taskIds.length-1] === vm.model.activityId">
                                                 the most recent </span>
@@ -269,12 +278,14 @@
                         <pre>{{vm.model.activity.detailedStatus}}</pre>
                     </br-collapsible>
 
-                    <br-collapsible ng-if="vm.model.activity.detailedStatus" state="vm.model.accordion.jsonOpen">
+                    <br-collapsible ng-if="vm.model.activity.id || vm.model.workflow.data" state="vm.model.accordion.jsonOpen">
                         <heading> JSON</heading>
                         <b>Activity</b>
                         <pre>{{vm.stringify(vm.model.activity)}}</pre>
-                        <b>Workflow</b>
-                        <pre>{{vm.stringify(vm.model.workflow)}}</pre>
+                        <div ng-if="vm.model.workflow.data">
+                            <b>Workflow</b>
+                            <pre>{{vm.stringify(vm.model.workflow)}}</pre>
+                        </div>
                     </br-collapsible>
 
                     <div>
diff --git a/ui-modules/app-inspector/app/views/main/inspect/management/detail/detail.template.html b/ui-modules/app-inspector/app/views/main/inspect/management/detail/detail.template.html
index cf0a4cd1..f563735d 100644
--- a/ui-modules/app-inspector/app/views/main/inspect/management/detail/detail.template.html
+++ b/ui-modules/app-inspector/app/views/main/inspect/management/detail/detail.template.html
@@ -87,7 +87,7 @@
                                 {{highlight.time > 0 ? (highlight.time | timeAgo) : 'ongoing'}}
                             </span>
                             <span class="utcTime fade" ng-show="showUTC">
-                                {{highlight.time > 0 ? (highlight.time | date : 'MMM dd, yyyy @ H:mm:ss.sss') : 'ongoing'}}
+                                {{highlight.time > 0 ? (highlight.time | dateFilter ) : 'ongoing'}}
                             </span>
                         </div>
                     </div>
diff --git a/ui-modules/home/app/views/about/about.controller.js b/ui-modules/home/app/views/about/about.controller.js
index 727da541..a5ab425e 100644
--- a/ui-modules/home/app/views/about/about.controller.js
+++ b/ui-modules/home/app/views/about/about.controller.js
@@ -251,6 +251,8 @@ export function aboutStateController($scope, $rootScope, $element, $q, $uibModal
 
 export function timeAgoFilter() {
     return function (input) {
+        if (!input || input<=0) return '-';
+
         if (input) {
             return fromNow(input);
         }