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 2023/06/20 02:05:05 UTC

[brooklyn-ui] 08/14: major tidy to workflow step, better focus on what is most interesting

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 5b45e74c69eefad93e49ac4e02e01c2076338456
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Sat Jun 17 18:29:53 2023 +0100

    major tidy to workflow step, better focus on what is most interesting
    
    collect input, output, and scratch vars; errors more prominent;
    less useful info pushed into collapsed sections; code blocks resizable
---
 .../components/workflow/workflow-step.directive.js |  85 ++++++--
 .../workflow/workflow-step.template.html           | 214 +++++++++++++--------
 .../app/components/workflow/workflow-steps.less    |  27 ++-
 .../inspect/activities/detail/detail.template.html |  52 ++---
 ui-modules/app-inspector/app/views/main/main.less  |   4 +
 5 files changed, 260 insertions(+), 122 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 55b2f1b2..fd81d670 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
@@ -74,12 +74,27 @@ export function workflowStepDirective() {
                 }
                 return null;
             };
+            vm.hasInterestingWorkflowNameFromReference = (ref, suffixIfFound) => {
+                const wn = vm.getWorkflowNameFromReference(ref, suffixIfFound);
+                return wn && wn.toLowerCase()!='sub-workflow';
+            };
+            vm.classForCodeMaybeMultiline = (obj) => {
+                let os = vm.yamlOrPrimitive(obj);
+                if (!os) return "simple-code";
+                os = ''+os;
+                if (os.endsWith('\n')) os = os.substring(0, os.length-1);
+                const lines = os.split('\n');
+                if (lines.length==1) return "simple-code";
+                if (lines.length <= 5) return "multiline-code lines-"+lines.length;
+                return "multiline-code multiline-code-resizable lines-"+lines.length;
+            };
 
             $scope.json = null;
             $scope.jsonMode = null;
-            vm.showJson = (mode, json) => {
+            vm.showAdditional = (title, mode, obj) => {
+                $scope.jsonTitle = title;
                 $scope.jsonMode = mode;
-                $scope.json = json ? stringify(json) : null;
+                $scope.json = obj==undefined ? null : vm.yamlOrPrimitive(obj);
             }
 
             $scope.stepTitle = {
@@ -108,6 +123,44 @@ export function workflowStepDirective() {
                 }
             }
 
+            function gatherOutputAndScratchForStep(osi, result, visited, isPrevious) {
+                if (result.output!=undefined && result.workflowScratch!=undefined) return ;
+                if (osi==undefined) {
+                    if (result.workflowScratchPartial != undefined) result.workflowScratch = result.workflowScratchPartial;
+                    delete result['workflowScratchPartial'];
+                    return;
+                }
+                // osi.woSc
+                let output = osi.context && osi.context.output;
+                if (isPrevious && output != undefined && result.output == undefined) result.output = output;
+                if (isPrevious && osi.workflowScratchUpdates != undefined) result.workflowScratchPartial =
+                    Object.assign({}, osi.workflowScratchUpdates, result.workflowScratchPartial);
+                if (osi.workflowScratch != undefined) result.workflowScratchPartial =
+                    Object.assign({}, osi.workflowScratchUpdates, result.workflowScratchPartial);
+
+                let next = undefined;
+                if (osi.previous && osi.previous.length) {
+                    const pp = osi.previous[0];
+                    if (!visited.includes(pp)) {
+                        visited.push(pp);
+                        next = $scope.workflow.data.oldStepInfo[pp];
+                    }
+                }
+                gatherOutputAndScratchForStep(next, result, visited, true);
+            }
+
+            const resultForStep = {};
+            vm.getOutputAndScratchForStep = (i) => {
+                if (!resultForStep[i]) {
+                    if (!$scope.workflow || !$scope.workflow.data || !$scope.workflow.data.oldStepInfo) return {};
+                    const osi = $scope.workflow.data.oldStepInfo[i] || null;
+                    if (!osi) return {};
+                    resultForStep[i] = {};
+                    gatherOutputAndScratchForStep(osi, resultForStep[i], [i], false);
+                }
+                return resultForStep[i];
+            }
+
             function updateData() {
                 let workflow = $scope.workflow;
                 workflow.data = workflow.data || {};
@@ -124,22 +177,32 @@ export function workflowStepDirective() {
                 $scope.isFocusTask = false;
                 $scope.isErrorHandler = $scope.workflow.tag && ($scope.workflow.tag.errorHandlerForTask);
 
-                $scope.stepCurrentError = (($scope.task || {}).currentStatus === 'Error') ? 'This step returned an error.'
-                    : ($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 && !$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;
-
                 if ($scope.task) {
                     if (!vm.isNullish($scope.stepContext.taskId) && $scope.stepContext.taskId === $scope.task.id) {
                         $scope.isFocusTask = true;
 
                     } else if ($scope.isFocusStep) {
-                        // TODO other instance of this tag selected
+                        // careful -- other instance of this step selected
                     }
                 }
+
+                $scope.stepCurrentError =
+                    ($scope.stepContext.error && !$scope.isFocusTask)
+                    ? 'This step had an error.'
+                    : ($scope.isWorkflowError && $scope.isCurrentMaybeInactive)
+                    ? 'The workflow encountered an error at this step.'  /* odd */
+                    : null;
+                const incomplete = $scope.osi.countStarted - $scope.osi.countCompleted > ($scope.isCurrentAndActive ? 1 : 0);
+
+                $scope.stepCurrentWarning = $scope.stepCurrentError ? null :
+                    $scope.stepContext.errorHandlerTaskId
+                    ? 'This step had an error which was handled.'
+                    : incomplete
+                    ? 'This step has previously had an error'
+                    : null;
+                $scope.stepCurrentSuccess = $scope.stepCurrentError || $scope.stepCurrentWarning ? null :
+                    (!$scope.isCurrentAndActive && !incomplete && $scope.osi.countCompleted > 0)
+                    ? 'This step has completed without errors.' : null;
             }
             $scope.$watch('workflow', updateData);
             updateData();
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 0a053a65..1f612cff 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
@@ -102,7 +102,7 @@
                             </span>
                             <span ng-if="!isCurrentAndActive">
                                 <span ng-if="osi.countStarted == 1">
-                                    This step had errors when it ran
+                                    This step had errors. It ran
                                 </span>
                                 <span ng-if="osi.countStarted >= 2 && osi.countCompleted==0">
                                     This step has had errors on all previous runs, including the last run,
@@ -114,134 +114,180 @@
                         </span>
 
                         <span ng-if="isFocusTask">
-                            in the task this page is focused on (<span class="monospace">{{ stepContext.taskId }}</span>).
-                            More details may be found in the other sections on this page.
+                            in task <span class="monospace">{{ stepContext.taskId }}</span> which is the one currently loaded on this page.
+                            The other sections on this page pertain specifically to this step.
                         </span>
                         <span ng-if="!isFocusTask">
-                            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>.
+                            in <button type="button" class="btn inline-button-small"
+                                   ui-sref="main.inspect.activities.detail({applicationId: workflow.applicationId, entityId: workflow.entityId, activityId: stepContext.taskId, workflowId })"
+                                >task <span class="monospace">{{ stepContext.taskId }}</span></button>.
                         </span>
                     </span>
                 </div>
 
                 <div ng-if="isErrorHandler" class="space-above">
-                    The task on this page is for the error handler for this step.
-                    More details may be found in the other sections on this page.
+                    The error handler for this step is the task currently loaded on this page.
+                    The other sections on this page pertain specifically to the error handler for this step.
                 </div>
 
                 <div ng-if="stepContext.errorHandlerTaskId && !isErrorHandler" class="space-above">
-                    The error triggered an error handler in
-                    <b><a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: stepContext.errorHandlerTaskId, workflowId})">
-                        <span class="monospace">task {{stepContext.errorHandlerTaskId}}</span
-                        ></a></b>.
+                    The error here triggered a handler in
+                    <button type="button" class="btn inline-button-small"
+                            ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: stepContext.errorHandlerTaskId, workflowId})"
+                        ><span class="monospace">task {{stepContext.errorHandlerTaskId}}</span></button
+                    ><span ng-if="stepCurrentWarning"> which successfully completed.</span
+                    ><span ng-if="stepCurrentError"> which threw an error.</span
+                    ><span ng-if="isCurrentAndActive"> which is running.</span
+                    >.
                 </div>
 
                 <div ng-if="isFocusStep && !isFocusTask && !isErrorHandler" class="space-above">
-                    <b>The activity currently being viewed (<span class="monospace">{{ task.id }}</span>) is for a previous run of this step.</b>
+                    <b>The task currently loaded on this page (<span class="monospace">{{ task.id }}</span>) is for a previous run of this step.</b>
                 </div>
 
-                <div class="more-space-above">
-                    <div class="data-row" ng-if="step.name"><div class="A">Name</div> <div class="B">{{ step.name }}</div></div>
-                    <div class="data-row" ng-if="step.id"><div class="A">ID</div> <div class="B fixed-width">{{ step.id }}</div></div>
-                    <div class="data-row"><div class="A">Step Number</div> <div class="B">{{ stepIndex+1 }}</div></div>
-                </div>
+                <div ng-if="!workflow.runIsOld">
+                    <div ng-if="stepContext.subWorkflows && stepContext.subWorkflows.length" class="space-above">
+                        <span ng-if="stepContext.subWorkflows.length==1" class="space-above">
+                            This step ran
 
-                <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.countStarted - osi.countCompleted - (isCurrentAndActive ? 1 : 0) }}</div></div>
-                </div>
+                            <button type="button" class="btn inline-button-small"
+                                    ui-sref="main.inspect.activities.detail({applicationId: stepContext.subWorkflows[0].applicationId, entityId: stepContext.subWorkflows[0].entityId, activityId: stepContext.subWorkflows[0].workflowId, workflowLatestRun: true})">
+                                {{ vm.hasInterestingWorkflowNameFromReference(stepContext.subWorkflows[0]) ?
+                                    '1 nested workflow: '+
+                                    vm.getWorkflowNameFromReference(stepContext.subWorkflows[0])
+                                    : '1 nested workflow ' }}
+                                (<span class="monospace">{{ stepContext.subWorkflows[0].workflowId }}</span>)
+                            </button>.
+                        </span>
+                        <span ng-if="stepContext.subWorkflows.length>1" class="space-above">
+                            This step ran
 
-                <div class="more-space-above" ng-if="stepContext.taskId">
-                    <div class="data-row">
-                        <div class="A"><span ng-if="isCurrentAndActive">CURRENT</span><span ng-if="!isCurrentAndActive">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, workflowId })"
-                                            >task <span class="monospace">{{ stepContext.taskId }}</span></a>
-                                    </span>
-                        </div>
-                    </div>
-                    <div ng-if="!isFocusStep || isFocusTask">
-                        <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 ng-if="stepContext.subWorkflows.length>1">
-                                <button id="workflow-button" type="button" class="btn btn-select-dropdown workflow-button-small" uib-dropdown-toggle style="padding-left: 9px; padding-right: 9px">
+                                <button id="nested-workflow-dropdown-button" type="button" class="btn inline-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" style="width: auto; max-width: 40em;">
+                                <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="nested-workflow-dropdown-button" style="width: auto; max-width: 40em;">
                                     <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, workflowLatestRun: true})" style="padding-left: 9px; padding-right: 9px;">
                                             <span>{{ vm.getWorkflowNameFromReference(sub, "-") }}</span>
                                             <span class="monospace">{{ sub.workflowId }}</span>
                                     </a></li>
                                 </ul>
-                            </div>
-                            <div class="btn-group" uib-dropdown ng-if="stepContext.subWorkflows.length==1">
-                                <a href="" ui-sref="main.inspect.activities.detail({applicationId: stepContext.subWorkflows[0].applicationId, entityId: stepContext.subWorkflows[0].entityId, activityId: stepContext.subWorkflows[0].workflowId, workflowLatestRun: true})">
-                                    <span>{{ vm.getWorkflowNameFromReference(stepContext.subWorkflows[0]) }}</span>
-                                    <span class="monospace">{{ stepContext.subWorkflows[0].workflowId }}</span>
-                                </a>
-                            </div>
-                        </div></div>
+                            </div>.
 
-                        <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, 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="!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, workflowId })"
-                                    >task <span class="monospace">{{ osi.nextTaskId }}</span></a>)
-                            </span>
-                            <span ng-if="!osi.nextTaskId">(workflow end)</span>
-                        </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="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.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="stepContext.output"><div class="A">Output</div> <div class="B multiline-code">{{ vm.yaml(stepContext.output) }}</div></div>
+                        </span>
                     </div>
                 </div>
 
                 <div class="more-space-above">
-                    <div class="data-row"><div class="A">Definition</div> <div class="B multiline-code">{{ vm.yamlOrPrimitive(step) }}</div></div>
-                </div>
+                    <div class="data-row" ng-if="stepContext.error"><div class="A">Error</div> <div class="B {{ vm.classForCodeMaybeMultiline(stepContext.error) }}">{{ vm.yamlOrPrimitive(stepContext.error) }}</div></div>
+
+                    <div class="data-row"><div class="A">Step Definition</div> <div class="B {{ vm.classForCodeMaybeMultiline(step) }}">{{ vm.yamlOrPrimitive(step) }}</div></div>
+
+                    <div ng-if="!isFocusStep || isFocusTask">
+                        <div class="data-row" ng-if="stepContext.otherMetadata" ng-repeat="(key,value) in stepContext.otherMetadata" id="$key">
+                            <div class="A">{{ key }}</div> <div class="B {{ vm.classForCodeMaybeMultiline(value) }}">{{ vm.yamlOrPrimitive(value) }}</div>
+                        </div>
 
+                        <div class="data-row" ng-if="stepContext.output"><div class="A">Output</div> <div class="B {{ vm.classForCodeMaybeMultiline(stepContext.output) }}">{{ vm.yaml(stepContext.output) }}</div></div>
+                        <div class="data-row" ng-if="osi.workflowScratchUpdates"><div class="A">Variables Updated</div> <div class="B {{ vm.classForCodeMaybeMultiline(osi.workflowScratchUpdates) }}">{{ vm.yaml(osi.workflowScratchUpdates) }}</div></div>
+                    </div>
+                </div>
             </div>
 
-            <div class="more-space-above" ng-if="vm.nonEmpty(stepContext) || vm.nonEmpty(step) || vm.nonEmpty(osi)">
+            <div class="more-space-above" ng-if="vm.nonEmpty(stepContext) || vm.nonEmpty(step) || vm.nonEmpty(osi)"
+                 style="margin-bottom: {{ jsonTitle ? '9px' : '6px' }};">
 
                 <div class="btn-group right" uib-dropdown>
-                    <button id="extra-data-button" type="button" class="btn btn-select-dropdown pull-right" uib-dropdown-toggle>
-                        JSON <span class="caret"></span>
+                    <button id="extra-data-button" type="button" class="btn btn-sm btn-select-dropdown pull-right" uib-dropdown-toggle
+                            style="padding: 2px 6px; {{ jsonTitle ? 'width: 100%; ' : '' }} text-align: right;">
+                        {{ jsonTitle ? 'Showing '+jsonTitle : 'More Information' }}
+                        <span class="caret" style="{{ jsonTitle ? 'rotate: 180deg;' : '' }}"></span>
                     </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'}">
+                    <ul class="dropdown-menu pull-right workflow-dropdown-small" uib-dropdown-menu role="menu" aria-labelledby="extra-data-button">
+                        <li role="menuitem" > <a href="" ng-click="vm.showAdditional('Context Variables', 'vars', null)" ng-class="{'selected' : jsonMode === 'vars'}">
                             <i class="fa fa-check check"></i>
-                            Last Execution Context</a> </li>
-                        <li role="menuitem" > <a href="" ng-click="vm.showJson('osi', osi)" ng-class="{'selected' : jsonMode === 'osi'}">
+                            Context Variables</a> </li>
+                        <li role="menuitem" > <a href="" ng-click="vm.showAdditional('Additional Step Info', 'metadata', null)" ng-class="{'selected' : jsonMode === 'summary'}">
                             <i class="fa fa-check check"></i>
-                            Executions Record</a> </li>
-                        <li role="menuitem" > <a href="" ng-click="vm.showJson('step', step)" ng-class="{'selected' : jsonMode === 'step'}">
+                            Additional Step Info</a> </li>
+                        <li role="menuitem" > <a href="" ng-click="vm.showAdditional('Step Record YAML', 'osi', osi)" ng-class="{'selected' : jsonMode === 'osi'}">
                             <i class="fa fa-check check"></i>
-                            Step Definition</a> </li>
-                        <li role="menuitem" > <a href="" ng-click="vm.showJson(null)" ng-class="{'selected' : jsonMode === null}">
+                            Step Record YAML</a> </li>
+                        <li role="menuitem" > <a href="" ng-click="vm.showAdditional(null, null)" ng-class="{'selected' : jsonMode === null}">
                             <i class="fa fa-check check"></i>
                             None</a> </li>
                     </ul>
                 </div>
 
-                <pre ng-if="json" class="space-above">{{ json }}</pre>
+                <div ng-if="jsonMode === 'vars'">
+                    <div class="data-row" ng-if="vm.getOutputAndScratchForStep(stepIndex).output">
+                        <div class="A">Output of previous</div>
+                        <div class="B {{ vm.classForCodeMaybeMultiline(vm.getOutputAndScratchForStep(stepIndex).output) }}">{{
+                            vm.yamlOrPrimitive(vm.getOutputAndScratchForStep(stepIndex).output) }}</div>
+                    </div>
+                    <div class="data-row" ng-if="vm.getOutputAndScratchForStep(stepIndex).workflowScratch">
+                        <div class="A">Workflow scratch</div>
+                        <div class="B {{ vm.classForCodeMaybeMultiline(vm.getOutputAndScratchForStep(stepIndex).workflowScratch) }}">{{
+                            vm.yamlOrPrimitive(vm.getOutputAndScratchForStep(stepIndex).workflowScratch) }}</div>
+                    </div>
+                    <div class="data-row" ng-if="workflow.data.input">
+                        <div class="A">Workflow input</div>
+                        <div class="B {{ vm.classForCodeMaybeMultiline(workflow.data.input) }}">{{
+                            vm.yamlOrPrimitive(workflow.data.input) }}</div>
+                    </div>
+                </div>
+                <div ng-if="jsonMode === 'metadata'">
+                        <div class="data-row" ng-if="step.name"><div class="A">Name</div> <div class="B">{{ step.name }}</div></div>
+                        <div class="data-row" ng-if="step.id"><div class="A">ID</div> <div class="B fixed-width">{{ step.id }}</div></div>
+                        <div class="data-row"><div class="A">Step Number</div> <div class="B">{{ stepIndex+1 }}</div></div>
+
+                        <div ng-if="!isFocusStep || isFocusTask">
+                            <!-- only show these if not looking at an earlier run of a step -->
+                            <div class="data-row more-space-above" ng-if="stepContext.input"><div class="A">All Input</div> <div class="B multiline-code">{{ vm.yaml(stepContext.input) }}</div></div>
+                        </div>
+
+                    <div class="data-row more-space-above">
+                            <div class="A"><span ng-if="isCurrentAndActive">CURRENTLY EXECUTING</span><span ng-if="!isCurrentAndActive"><span ng-if="osi.countStarted > 1">LAST </span>EXECUTED</span> IN</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, workflowId })"
+                                                 >task <span class="monospace">{{ stepContext.taskId }}</span></a>
+                                            </span>
+                            </div>
+                        </div>
+                        <div ng-if="!isFocusStep || isFocusTask">
+                            <!-- only show these if not looking at an earlier run of a step -->
+                            <div class="data-row"><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, workflowId })"
+                                >task <span class="monospace">{{ osi.previousTaskId }}</span></a>)
+                                </span>
+                                <span ng-if="!osi.previousTaskId">(workflow start)</span>
+                            </div></div>
+
+                            <div class="data-row" 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, workflowId })"
+                                >task <span class="monospace">{{ osi.nextTaskId }}</span></a>)
+                                </span>
+                                <span ng-if="!osi.nextTaskId">(workflow end)</span>
+                            </div></div>
+                        </div>
+
+                        <div ng-if="osi.countStarted > 1 && osi.countStarted > osi.countCompleted" class="space-above">
+                            <div class="data-row more-space-above"><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.countStarted - osi.countCompleted - (isCurrentAndActive ? 1 : 0) }}</div></div>
+                        </div>
+                    </div>
+
+                <pre ng-if="json" class="more-space-above">{{ json }}</pre>
             </div>
         </div>
 
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 5eb05e38..bd96ee38 100644
--- a/ui-modules/app-inspector/app/components/workflow/workflow-steps.less
+++ b/ui-modules/app-inspector/app/components/workflow/workflow-steps.less
@@ -181,9 +181,6 @@
         }
       }
     }
-    .workflow-button-small {
-      padding: 1px 5px;
-    }
   }
 
   .workflow-step-status-indicators {
@@ -242,12 +239,24 @@
     //}
   }
 
-  .multiline-code {
+  .simple-code, .multiline-code {
     white-space: pre;
     overflow: scroll;
+  }
+  .simple-code {
+    padding: 3px;
+  }
+  .multiline-code.multiline-code-resizable {
+    resize: vertical;
+    height: 100px;
+    max-height: max-content;
+    border: 1px solid @gray-lighter;
+  }
+  .multiline-code {
     max-height: 100px;
+    padding: 2px;
   }
-  .multiline-code, .fixed-width {
+  .simple-code, .multiline-code, .fixed-width {
     .monospace();
     font-size: 85%;
   }
@@ -269,3 +278,11 @@
   }
 
 }
+
+.dropdown-menu.workflow-dropdown-small {
+  min-width: 120px;
+  li > a {
+    padding: 3px 16px;
+    font-size: 12px;
+  }
+}
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 86f87c0e..652ce3d8 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
@@ -227,9 +227,10 @@
                                             <span ng-if="vm.model.workflow.runIsOld">a previous run of </span>
                                             <span ng-if="vm.model.workflow.runIsLatest">the most recent run of </span>
                                         </span>
-                                        <a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.taskId, workflowId})">
+                                        <button type="button" class="btn inline-button-small"
+                                                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>.
+                                            <b>{{vm.model.workflow.data.name}}</b></button>.
                                     </span>
                                     <span ng-if="vm.isNullish(vm.model.workflow.tag.stepIndex) && !vm.model.activity.id">
                                         part of
@@ -240,28 +241,32 @@
                                         for
                                         <span ng-if="vm.model.workflow.runIsOld" style="font-weight: bold;">
                                             a previous run of
-                                            <a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.taskId, workflowId})">
+                                            <button type="button" class="btn inline-button-small"
+                                                    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>
+                                                <b>{{vm.model.workflow.data.name}}</b>.</button>
                                         </span>
                                         <span ng-if="!vm.model.workflow.runIsOld">
                                             <span ng-if="vm.model.workflow.runIsLatest && vm.model.workflow.runMultipleTimes">the most recent run 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.isError && vm.model.workflow.data.currentStepInstance && vm.model.workflow.data.currentStepInstance.taskId">
+                                        <div ng-if="vm.model.workflow.isError && vm.model.workflow.data.currentStepInstance && vm.model.workflow.data.currentStepInstance.taskId"
+                                                style="margin-top: 6px;">
                                             Details of the failure can be found by opening
-                                            <a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.currentStepInstance.taskId, workflowId})">
-                                                the failed task</a>.
-                                        </span>
+                                            <button type="button" class="btn inline-button-small"
+                                                    ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.currentStepInstance.taskId, workflowId})">
+                                                the failed task</button>.
+                                        </div>
                                     </span>
                                 </div>
 
                                 <div ng-if="vm.model.workflow.data.parentId" class="workflow-preface-para">
                                     This is a sub-workflow in
-                                    <b><a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.parentId, workflowId: vm.model.workflow.data.parentId, workflowLatestRun: true})">
+                                    <button type="button" class="btn inline-button-small"
+                                            ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.parentId, workflowId: vm.model.workflow.data.parentId, workflowLatestRun: true})">
                                         workflow <span class="monospace">{{vm.model.workflow.data.parentId}}</span
-                                    ></a></b>.
+                                    ></button>.
                                 </div>
 
                                 <div ng-if="vm.model.workflow.finishedWithNoSteps" class="workflow-preface-para">
@@ -275,34 +280,37 @@
                                     </span>
                                     <span ng-if="vm.model.activityId!=vm.model.workflow.data.errorHandlerTaskId">
                                         This workflow had an error which ran error handler
-                                        <b><a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.errorHandlerTaskId, workflowId})">
+                                        <button type="button" class="btn inline-button-small"
+                                                ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.data.errorHandlerTaskId, workflowId})">
                                             <span class="monospace">task {{vm.model.workflow.data.errorHandlerTaskId}}</span
-                                        ></a></b>.
+                                        ></button>.
                                     </span>
                                 </div>
 
                                 <div ng-if="vm.model.workflow.runIsOld" class="workflow-preface-para">
                                     For previous runs such as this, the subtask view
                                     <span ng-if="!vm.isNullish(vm.model.workflow.tag.stepIndex)">for
-                                        <a ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.runReplayId, workflowId})">
+                                        <button type="button" class="btn inline-button-small"
+                                                ui-sref="main.inspect.activities.detail({entityId: vm.model.entityId, activityId: vm.model.workflow.runReplayId, workflowId})">
                                             the relevant run
-                                        </a>
+                                        </button>
                                     </span>
                                     <span ng-if="vm.isNullish(vm.model.workflow.tag.stepIndex)">
                                         <span ng-if="showOldWorkflowRunStepDetails">further</span>
                                         below
                                     </span>
-                                    is normally more informative than workflow steps.
+                                    is normally more informative than workflow steps which focus on the most recent execution.
                                     <span ng-if="!showOldWorkflowRunStepDetails">
-                                        However the workflow step view
-                                        <a href="" ng-click="vm.toggleOldWorkflowRunStepDetails()">
-                                            can be shown</a>
-                                        below if desired.
+                                        If the non-replay-specific workflow step view is desired here, it
+                                        can <button type="button" class="btn inline-button-small"
+                                                ng-click="vm.toggleOldWorkflowRunStepDetails()">
+                                            show below</button>.
                                     </span>
                                     <span ng-if="showOldWorkflowRunStepDetails">
-                                        The workflow step view immediately below
-                                        <a href="" ng-click="vm.toggleOldWorkflowRunStepDetails()">
-                                            can be hidden</a>.
+                                        If the non-replay-specific workflow step view is no longer wanted, it can
+                                        <button type="button" class="btn inline-button-small"
+                                                ng-click="vm.toggleOldWorkflowRunStepDetails()">
+                                            hide</button>.
                                     </span>
                                 </div>
 
diff --git a/ui-modules/app-inspector/app/views/main/main.less b/ui-modules/app-inspector/app/views/main/main.less
index 951d934b..a78a4af9 100644
--- a/ui-modules/app-inspector/app/views/main/main.less
+++ b/ui-modules/app-inspector/app/views/main/main.less
@@ -65,3 +65,7 @@
   }
 }
 
+.inline-button-small {
+  vertical-align: baseline;
+  padding: 1px 6px;
+}
\ No newline at end of file