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:11 UTC
[brooklyn-ui] 02/24: workflow UI polish, subworkflows, bug-fixes
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 0714fb85b637c070e5daac8b869f82d2c9d4ee8a
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Tue Oct 4 23:03:40 2022 +0100
workflow UI polish, subworkflows, bug-fixes
---
.../components/workflow/workflow-step.directive.js | 1 +
.../workflow/workflow-step.template.html | 34 ++++++---
.../workflow/workflow-steps.directive.js | 2 +-
.../app/components/workflow/workflow-steps.less | 7 ++
.../inspect/activities/detail/detail.controller.js | 70 ++++++++++++++-----
.../main/inspect/activities/detail/detail.less | 31 +++++++--
.../inspect/activities/detail/detail.template.html | 81 ++++++++++++++++------
7 files changed, 174 insertions(+), 52 deletions(-)
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 9a322297..e22cf1e2 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
@@ -61,6 +61,7 @@ export function workflowStepDirective() {
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;
$scope.json = null;
$scope.jsonMode = null;
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 b3ac63c9..a9d4848b 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
@@ -97,15 +97,15 @@
</span>
<span ng-if="isFocusTask">
- in this task ({{ stepContext.taskId }}).
+ as the activity currently being viewed (<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 {{ stepContext.taskId }}</a>.
+ 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>.
</span>
</div>
<div ng-if="isFocusStep && !isFocusTask" class="space-above">
- <b>The currently selected task ({{ task.id }}) is for a previous invocation of this step.</b>
+ <b>The activity currently being viewed (<span class="monospace">{{ task.id }}</span>) is for a previous run of this step.</b>
</div>
<div class="more-space-above">
@@ -126,10 +126,11 @@
<div class="A"><span ng-if="isCurrent">CURRENT</span><span ng-if="!isCurrent">LAST</span> EXECUTION</div>
<div class="B">
<span ng-if="isFocusTask">
- Task {{ stepContext.taskId }}
+ 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 })">Task {{ stepContext.taskId }}</a>
+ <a ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: stepContext.taskId })"
+ >task <span class="monospace">{{ stepContext.taskId }}</span></a>
</span>
</div>
</div>
@@ -138,19 +139,34 @@
<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 })"
- >Task {{ osi.previousTaskId }}</a>)
+ >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">
<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 })"
- >Task {{ osi.nextTaskId }}</a>)
+ >task <span class="monospace">{{ osi.nextTaskId }}</span></a>)
</span>
<span ng-if="!osi.nextTaskId">(workflow end)</span>
</div></div>
+ <div class="data-row nested with-buttons" ng-if="stepContext.subWorkflows && stepContext.subWorkflows.length"><div class="A" style="margin-top: 2px;">Sub-workflows</div> <div class="B">
+ <div class="btn-group" uib-dropdown>
+ <button id="workflow-button" type="button" class="btn btn-select-dropdown workflow-button-small" uib-dropdown-toggle>
+ {{ stepContext.subWorkflows.length }} nested workflow{{ stepContext.subWorkflows.length>1 ? 's' : '' }} <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="workflow-button">
+ <li role="menuitem" ng-repeat="sub in stepContext.subWorkflows" id="sub-workflow-{{ sub.workflowId }}">
+ <a href="" ui-sref="main.inspect.activities.detail({applicationId: sub.applicationId, entityId: sub.entityId, activityId: sub.workflowId})">
+ <i class="fa fa-check check"></i>
+ <span class="monospace">{{ sub.workflowId }}</span></a> </li>
+ </ul>
+ </div>
+ </div></div>
+
<div class="data-row nested" ng-if="osi.workflowScratch"><div class="A">Workflow Vars</div> <div class="B multiline-code">{{ vm.yaml(osi.workflowScratch) }}</div></div>
<div class="data-row nested" ng-if="stepContext.input"><div class="A">Input</div> <div class="B multiline-code">{{ vm.yaml(stepContext.input) }}</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>
@@ -165,10 +181,10 @@
<div class="more-space-above" ng-if="vm.nonEmpty(stepContext) || vm.nonEmpty(step) || vm.nonEmpty(osi)">
<div class="btn-group right" uib-dropdown>
- <button id="single-button" type="button" class="btn btn-select-dropdown pull-right" uib-dropdown-toggle>
+ <button id="extra-data-button" type="button" class="btn btn-select-dropdown pull-right" uib-dropdown-toggle>
View data <span class="caret"></span>
</button>
- <ul class="dropdown-menu pull-right" uib-dropdown-menu role="menu" aria-labelledby="single-button">
+ <ul class="dropdown-menu pull-right" uib-dropdown-menu role="menu" aria-labelledby="extra-data-button">
<li role="menuitem" > <a href="" ng-click="vm.showJson('stepContext', stepContext)" ng-class="{'selected' : jsonMode === 'stepContext'}">
<i class="fa fa-check check"></i>
Last Execution Context</a> </li>
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 1d509ada..8eb950d0 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
@@ -54,7 +54,7 @@ export function workflowStepsDirective() {
}
$scope.expandStates = {};
- if ($scope.workflow.tag && $scope.workflow.tag.stepIndex) {
+ if ($scope.workflow.tag && !_.isNil($scope.workflow.tag.stepIndex)) {
$scope.expandStates[$scope.workflow.tag.stepIndex] = true;
}
diff --git a/ui-modules/app-inspector/app/components/workflow/workflow-steps.less b/ui-modules/app-inspector/app/components/workflow/workflow-steps.less
index 0a9bd803..2c067037 100644
--- a/ui-modules/app-inspector/app/components/workflow/workflow-steps.less
+++ b/ui-modules/app-inspector/app/components/workflow/workflow-steps.less
@@ -122,6 +122,7 @@
display: flex;
margin-top: 3px;
margin-bottom: 3px;
+ align-items: baseline;
.A {
flex: 0 0 auto;
width: 30%;
@@ -134,6 +135,9 @@
}
.B {
flex: 1 1 auto;
+ &.multiline-code {
+ margin-top: 3px;
+ }
}
&.nested {
@@ -171,6 +175,9 @@
}
}
}
+ .workflow-button-small {
+ padding: 1px 5px;
+ }
}
.workflow-step-status-indicators {
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 02388f97..7ef65aba 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
@@ -43,6 +43,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
activityId: activityId,
childFilter: {'EFFECTOR': true, 'SUB-TASK': false},
accordion: {summaryOpen: true, subTaskOpen: true, streamsOpen: true, workflowOpen: true},
+ activity: {},
workflow: {},
};
@@ -52,7 +53,39 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
let observers = [];
if ($state.current.name === detailState.name) {
-
+
+ function loadWorkflow(workflowTag, optimistic) {
+ if (!workflowTag) {
+ workflowTag = {}
+ optimistic = true;
+ }
+ 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};
+ if (optimistic) {
+ vm.model.workflow.tag = workflowTag;
+ }
+ vm.model.workflow.data = wResponse.data;
+ vm.model.workflow.loading = 'loaded';
+ vm.model.workflow.applicationId = workflowTag.applicationId;
+ vm.model.workflow.entityId = workflowTag.entityId;
+
+ observers.push(wResponse.subscribe((wResponse2)=> {
+ // change the workflow object so widgets get refreshed
+ vm.model.workflow = { ...vm.model.workflow, data: wResponse2.data };
+ }));
+ }).catch(error => {
+ if (optimistic) {
+ vm.model.workflow.loading = null;
+ throw error;
+ }
+
+ console.log("ERROR loading workflow " + workflowTag.workflowId, error);
+ vm.model.workflow.loading = 'error';
+ });
+ };
+
activityApi.activity(activityId).then((response)=> {
vm.model.activity = response.data;
@@ -72,21 +105,7 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
const workflowTag = findWorkflowTag(vm.model.activity);
if (workflowTag) {
vm.model.workflow.tag = workflowTag;
- vm.model.workflow.loading = 'loading';
- entityApi.getWorkflow(applicationId, entityId, workflowTag.workflowId).then(wResponse => {
- vm.model.workflow.data = wResponse.data;
- vm.model.workflow.loading = 'loaded';
- vm.model.workflow.applicationId = applicationId;
- vm.model.workflow.entityId = entityId;
-
- observers.push(wResponse.subscribe((wResponse2)=> {
- // change the workflow object so widgets get refreshed
- vm.model.workflow = { ...vm.model.workflow, data: wResponse2.data };
- }));
- }).catch(error => {
- console.log("ERROR loading workflow " + workflowTag.workflowId, error);
- vm.model.workflow.loading = 'error';
- });
+ loadWorkflow(workflowTag);
}
}
@@ -96,12 +115,24 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
vm.error = undefined;
vm.errorBasic = false;
}));
+
}).catch((error)=> {
$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.');
+
+ // in case it corresponds to a workflow and not a task, try loading as a workflow
+
+ loadWorkflow(null).then(()=> {
+ // 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.');
+
+ }).catch(error2 => {
+ $log.debug("ID "+activityId+" does not correspond to workflow either", error2);
+ });
});
activityApi.activityChildren(activityId).then((response)=> {
@@ -173,7 +204,6 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
$timeout(function() { $scope.$broadcast('resize') }, 100);
};
- vm.stringifyActivity = () => JSON.stringify(vm.model.activity, null, 2);
vm.stringify = (data) => JSON.stringify(data, null, 2);
vm.invokeEffector = (effectorName, effectorParams) => {
@@ -206,6 +236,12 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
// so transient tasks etc less relevant
}
+ vm.showReplayHelp = () => {
+ $scope.showReplayHelp = !$scope.showReplayHelp;
+ }
+
+ vm.isNullish = _.isNil;
+
}
function findWorkflowTag(task) {
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 b6eb05e5..2dc74ee4 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
@@ -106,10 +106,6 @@
.monospace();
}
- .monospace {
- .monospace();
- }
-
.table-responsive,
.table {
margin-bottom: 0;
@@ -161,7 +157,7 @@
}
}
}
- .result-parent {
+ .monospace, .result-parent {
.monospace();
}
.result-parent.big-result {
@@ -190,4 +186,29 @@
margin-top: -12px;
}
+ .workflow-body {
+ .btn-group {
+ > .dropdown-menu {
+ li a {
+ padding-left: 2em;
+ }
+ }
+
+ .selected {
+ .check {
+ margin-left: -1.5em;
+ display: block;
+ width: 0;
+ height: 0;
+ overflow: visible;
+ margin-top: 3px;
+ margin-bottom: -3px;
+ }
+ }
+ .check {
+ display: none;
+ }
+ }
+ }
+
}
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 25eada16..09e45831 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
@@ -18,9 +18,9 @@
-->
<ui-view class="activity-any">
<div>
- <loading-state error="vm.error" trust-error="true" ng-if="!vm.model.activity || !vm.model.activityChildren"></loading-state>
+ <loading-state error="vm.error" trust-error="true" ng-if="!vm.model.activity.id || !vm.model.activityChildren"></loading-state>
- <div ng-if="vm.model.activity && vm.model.activityChildren">
+ <div ng-if="vm.model.activity.id && vm.model.activityChildren">
<ol class="breadcrumb" ng-show="showParents">
<li breadcrumb-navigation parent-id="{{vm.model.activity.submittedByTask.metadata.id}}"
entity-id="{{vm.model.entityId}}"></li>
@@ -34,22 +34,24 @@
</li>
<li class="breadcrumb-item active">{{vm.model.activity.displayName}}</li>
</ol>
+ </div>
- <div ng-if="vm.model.activity" class="activity-detail">
+ <div>
+ <div class="activity-detail">
<div class="alert alert-info" ng-if="vm.model.activity.blockingTask">
<strong>Blocked on:</strong>
<span ng-if="vm.model.activity.blockingDetails">{{vm.model.activity.blockingDetails}}:</span>
<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 class="activity-header">
+ <div class="activity-header" ng-if="vm.model.activity.id">
<div class="activity-title">{{vm.model.activity.displayName}}</div>
<div class="activity-entity">{{vm.model.activity.entityDisplayName}}</div>
<div class="activity-description" ng-if="vm.model.activity.description">{{vm.model.activity.description}}</div>
</div>
<div class="activity-body">
- <div class="summary-body">
+ <div class="summary-body" ng-if="vm.model.activity.id">
<div class="summary-block">
<div class="row">
<div class="col-md-3 summary-item">
@@ -158,21 +160,56 @@
</br-collapsible>
<br-collapsible state="vm.model.accordion.workflowOpen"
- ng-if="vm.model.workflow">
+ ng-if="vm.model.workflow.data">
<heading> Workflow</heading>
<div class="workflow-body">
<div ng-if="vm.model.workflow.loading == 'loaded'">
- <p style="margin-top: 12px; margin-bottom: 24px;">
+ <div ng-if="vm.model.workflow.data.taskIds.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}">
+ <i class="fa fa-check check"></i>
+ <span class="monospace">{{ id }}</span></a> </li>
+ <li role="menuitem">
+ <a href="" ng-click="vm.showReplayHelp()" ng-class="{'selected' : showReplayHelp}"><i>More information</i></a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div style="margin-top: 12px; margin-bottom: 24px;">
This task is for
- <span ng-if="vm.model.workflow.tag.stepIndex">step <b>{{ vm.model.workflow.tag.stepIndex+1 }}</b>
- in workflow
+ <span ng-if="!vm.isNullish(vm.model.workflow.tag.stepIndex)">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})">
- <b>{{vm.model.workflow.data.name}}</b>.
- </a>
+ 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.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>
+ <span ng-if="vm.model.workflow.data.taskIds[vm.model.workflow.data.taskIds.length-1] !== vm.model.activityId">
+ run {{vm.model.workflow.data.taskIds.indexOf(vm.model.activityId)+1}} </span>
+ of {{ vm.model.workflow.data.taskIds.length }} of
+ </span>
+ workflow <span class="monospace">{{vm.model.workflow.data.workflowId}}</span>:
+ <b>{{vm.model.workflow.data.name}}</b>.
</span>
- <span ng-if="!vm.model.workflow.tag.stepIndex"> workflow <b>{{vm.model.workflow.data.name}}</b>.</span>
- </p>
+ </div>
+ <div ng-if="showReplayHelp" style="margin-top: 12px; margin-bottom: 24px;">
+ Workflows can be replayed in certain situations, such as if they fail or the server is restarted.
+ This workflow invocation instance has been replayed, with a total of {{ vm.model.workflow.data.taskIds.length }} runs.
+ Individual replays can be viewed by selecting a task ID from the dropdown.
+ The workflow step data below shows the most recent run of each step in any replay.
+ The arrows between steps show all step transitions in any replay of this workflow instance.
+ Sub-task and log views further below can be useful to disambiguate multiple replays if required.
+ </div>
+
<workflow-steps workflow="vm.model.workflow" task="vm.model.activity"></workflow-steps>
</div>
<div ng-if="vm.model.workflow.loading != 'loaded'">
@@ -211,19 +248,23 @@
<br-collapsible ng-if="vm.model.activity.detailedStatus" state="vm.model.accordion.jsonOpen">
<heading> JSON</heading>
- <pre>{{vm.stringifyActivity()}}</pre>
+ <b>Activity</b>
+ <pre>{{vm.stringify(vm.model.activity)}}</pre>
+ <b>Workflow</b>
+ <pre>{{vm.stringify(vm.model.workflow)}}</pre>
</br-collapsible>
+ <div>
+ <br-collapsible state="vm.model.accordion.logbookOpen">
+ <heading> Logbook (activity)</heading>
+ <br-logbook task-id="{{vm.model.activityId}}"></br-logbook>
+ </br-collapsible>
+ </div>
+
</div>
</div>
</div>
- <div>
- <br-collapsible state="vm.model.accordion.logbookOpen">
- <heading> Logbook (activity)</heading>
- <br-logbook task-id="{{vm.model.activityId}}"></br-logbook>
- </br-collapsible>
- </div>
</div>
</ui-view>