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:14 UTC
[brooklyn-ui] 05/24: expand actions, move to top-right
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 0526f027e1c1682e56f71ca7cb360f141408ff28
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Wed Oct 5 15:51:33 2022 +0100
expand actions, move to top-right
supports cancel, most of what we need for replaying also.
tweaks to the nested dropdown so that it works as we want.
---
.../components/providers/activity-api.provider.js | 8 +-
.../inspect/activities/detail/detail.controller.js | 31 +++++++-
.../main/inspect/activities/detail/detail.less | 30 ++++++--
.../inspect/activities/detail/detail.template.html | 31 ++++++--
.../inspect/activities/detail/dropdown-nested.js | 90 ++++++++++++++++++----
5 files changed, 159 insertions(+), 31 deletions(-)
diff --git a/ui-modules/app-inspector/app/components/providers/activity-api.provider.js b/ui-modules/app-inspector/app/components/providers/activity-api.provider.js
index cdb36630..27deb5cc 100644
--- a/ui-modules/app-inspector/app/components/providers/activity-api.provider.js
+++ b/ui-modules/app-inspector/app/components/providers/activity-api.provider.js
@@ -39,7 +39,8 @@ function ActivityApi($http) {
activity: getActivity,
activityChildren: getActivityChildren,
activityDescendants: getActivityDescendants,
- activityStream: getActivityStream
+ activityStream: getActivityStream,
+ cancelActivity: cancelActivity,
};
function getActivities() {
@@ -64,4 +65,9 @@ function ActivityApi($http) {
}
}});
}
+
+ function cancelActivity(activityId) {
+ return $http.post('/v1/activities/' + activityId + '/cancel');
+ }
+
}
\ No newline at end of file
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 7ef65aba..9a9e1e0c 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
@@ -25,7 +25,7 @@ export const detailState = {
url: '/:activityId',
template: template,
controller: ['$scope', '$state', '$stateParams', '$log', '$uibModal', '$timeout', '$sanitize', '$sce', 'activityApi', 'entityApi', 'brUtilsGeneral', DetailController],
- controllerAs: 'vm'
+ controllerAs: 'vm',
}
function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeout, $sanitize, $sce, activityApi, entityApi, Utils) {
$scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
@@ -71,10 +71,27 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
vm.model.workflow.applicationId = workflowTag.applicationId;
vm.model.workflow.entityId = workflowTag.entityId;
+ vm.actions.workflowReplays = [];
+ if (!vm.model.activity.endTimeUtc || vm.model.activity.endTimeUtc<=0) {
+ // can't replay if active (same logic as 'cancel')
+ } else {
+ [
+ // TODO get from server
+ // [ 'step 3 (continuing)', null ],
+ // [ 'step 3 (replay point)', [2] ],
+ // [ 'start (replay point)', [0] ],
+ ].forEach(r => vm.actions.workflowReplays.push(r));
+ vm.actions.workflowReplays.forEach(r => {
+ r.push( () => console.log("TODO - replay from "+r[0], r[1]) );
+ })
+ }
+ if (!vm.actions.workflowReplays.length) delete vm.actions['workflowReplays'];
+
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;
@@ -89,7 +106,9 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
activityApi.activity(activityId).then((response)=> {
vm.model.activity = response.data;
- vm.actions = {};
+ vm.actions = vm.actions || {};
+ delete vm.actions['effector'];
+ delete vm.actions['invokeAgain'];
if ((vm.model.activity.tags || []).find(t => t=="EFFECTOR")) {
const effectorName = (vm.model.activity.tags.find(t => t.effectorName) || {}).effectorName;
const effectorParams = (vm.model.activity.tags.find(t => t.effectorParams) || {}).effectorParams;
@@ -101,6 +120,11 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
}
}
+ delete vm.actions['cancel'];
+ if (!vm.model.activity.endTimeUtc || vm.model.activity.endTimeUtc<=0) {
+ vm.actions.cancel = { doAction: () => { activityApi.cancelActivity(activityId); } };
+ }
+
if ((vm.model.activity.tags || []).find(t => t=="WORKFLOW")) {
const workflowTag = findWorkflowTag(vm.model.activity);
if (workflowTag) {
@@ -241,7 +265,8 @@ function DetailController($scope, $state, $stateParams, $log, $uibModal, $timeou
}
vm.isNullish = _.isNil;
-
+ vm.isEmpty = x => vm.isNullish(x) || (x.length==0) || (typeof x === 'object' && !Object.keys(x).length);
+ vm.isNonEmpty = x => !vm.isEmpty(x);
}
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 2dc74ee4..63068c45 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
@@ -21,6 +21,7 @@
.activity-header {
-webkit-font-smoothing: antialiased;
margin-bottom: 40px;
+
status {
width: 50px;
height: 50px;
@@ -50,7 +51,7 @@
color: @gray-light;
}
}
-
+
table.summary {
tr td:first-child {
width: 25%;
@@ -69,7 +70,7 @@
.summary-body {
margin-bottom: 24px;
}
-
+
.summary-item {
margin-bottom: 35px;
@@ -77,20 +78,24 @@
@redColor: @brand-danger;
@greenColor: #58BA58;
@redColor: #BA5858;
+
.summary-item-value {
&.status-completed {
color: @greenColor;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}
+
&.status-failed {
color: @redColor;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}
+
&.status-in-progress {
}
+
&.status-unknown {
}
@@ -137,6 +142,7 @@
> div {
opacity: 1.0;
position: absolute;
+
&.fade.ng-hide {
opacity: 0;
}
@@ -157,20 +163,23 @@
}
}
}
+
.monospace, .result-parent {
.monospace();
}
+
.result-parent.big-result {
- border: 1px solid @gray-lighter;
- .result-body {
- padding: 4px;
- }
+ border: 1px solid @gray-lighter;
+ .result-body {
+ padding: 4px;
+ }
}
+
.result-body {
max-height: 56pt;
overflow: scroll;
}
-
+
.collapsing {
// internal class used by bootstrap when opening/closing:
// activity viewers are probably power users - don't want to
@@ -205,10 +214,17 @@
margin-bottom: -3px;
}
}
+
.check {
display: none;
}
}
}
+}
+.dropdown-at-root {
+ width: auto;
+ &.dropdown-submenu-left, .dropdown-submenu-left {
+ margin-right: 36px;
+ }
}
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 09e45831..254435f4 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
@@ -38,12 +38,38 @@
<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 style="float:right;" ng-if="vm.isNonEmpty(vm.actions)" class="btn-group dropdown-nested dropdown-menu-right" uib-dropdown-nested dropdown-append-to-body="true">
+ <br-button uib-dropdown-toggle-nested type="btn-primary">
+ Actions <span class="caret"></span>
+ </br-button>
+ <ul uib-dropdown-menu-nested class="dropdown-at-root dropdown-menu-right">
+
+ <li><a href="" ng-if="vm.actions.cancel" ng-click="vm.actions.cancel.doAction()">Cancel</a></li>
+
+ <li ng-if="vm.actions.workflowReplays" uib-dropdown-nested dropdown-append-to-body="true">
+
+ <a href="" uib-dropdown-toggle-nested>Replay workflow <span class="caret"></span></a>
+ <ul class="dropdown-submenu-left dropdown-at-root dropdown-menu-right" uib-dropdown-menu-nested>
+ <li ng-repeat="replay in vm.actions.workflowReplays track by $index" id="replay {{ replay[0] }}">
+ <a class="dropdown-item" href="" ng-click="replay[2]()">From {{ replay[0] }}</a>
+ </li>
+ </ul>
+
+ </li>
+
+ <li><a href="" ng-if="vm.actions.invokeAgain" ng-click="vm.actions.invokeAgain.doAction()">Reinvoke effector</a></li>
+ <li><a href="" ng-if="vm.actions.effector" ui-sref="main.inspect.effectors({search: vm.actions.effector.effectorName})">Open in effector tab</a></li>
+
+ </ul>
+ </div>
+
<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>
@@ -126,11 +152,6 @@
</div>
</div>
- <div class="activity-actions" ng-if="vm.actions">
- <br-button ng-if="vm.actions.invokeAgain" on-click="vm.actions.invokeAgain.doAction()">Execute again</br-button>
- <br-button ng-if="vm.actions.effector" ui-sref="main.inspect.effectors({search: vm.actions.effector.effectorName})">Open effector tab</br-button>
- </div>
-
<br-collapsible class="activity-streams"
ng-if="vm.isNonEmpty(vm.model.activity.streams)"
state="vm.model.accordion.streamsOpen">
diff --git a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js
index 6d983bb9..00680a61 100644
--- a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js
+++ b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js
@@ -1,3 +1,5 @@
+import {drop} from "lodash/array";
+
const MODULE_NAME = 'ui.bootstrap.dropdown.nested';
export default MODULE_NAME;
@@ -11,6 +13,7 @@ angular.module(MODULE_NAME, ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
.service('uibDropdownServiceNested', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
var openScope = null;
+ var oldOpenScopes = [];
var openedContainers = $$multiMap.createNew();
this.isOnlyOpen = function(dropdownScope, appendTo) {
@@ -37,7 +40,8 @@ angular.module(MODULE_NAME, ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
}
if (openScope && openScope !== dropdownScope) {
- openScope.isOpen = false;
+ // just remember it
+ oldOpenScopes.push(openScope);
}
openScope = dropdownScope;
@@ -65,9 +69,15 @@ angular.module(MODULE_NAME, ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
this.close = function(dropdownScope, element, appendTo) {
if (openScope === dropdownScope) {
+ openScope = null;
+ }
+ const indexOfOpen = oldOpenScopes.indexOf(dropdownScope);
+ if (indexOfOpen>=0) {
+ oldOpenScopes.splice(indexOfOpen, 1);
+ }
+ if (openScope==null && oldOpenScopes.length) {
$document.off('click', closeDropdown);
$document.off('keydown', this.keybindFilter);
- openScope = null;
}
if (!appendTo) {
@@ -92,28 +102,78 @@ angular.module(MODULE_NAME, ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
var closeDropdown = function(evt) {
// This method may still be called during the same mouse event that
// unbound this event handler. So check openScope before proceeding.
- if (!openScope || !openScope.isOpen) { return; }
+ let scopesToApply = [];
+
+ function containsNested(container, target) {
+ if (!container) return false;
+ if (container==target) return true;
+ if (container[0] && container[0].contains && container[0].contains(target)) return true;
+ if (container.contains && container.contains(target)) return true;
+
+ let kids = angular.element(container).children();
+ if (kids && kids.length) {
+ for (let i=0; i<kids.length; i++) {
+ let found = containsNested(kids[i], target);
+ if (found) return true;
+ }
+ }
+ return false;
+ }
- if (evt && openScope.getAutoClose() === 'disabled') { return; }
+ function isAnyTrigger(element) {
+ return element.hasClass('dropdown-toggle');
+ }
- if (evt && evt.which === 3) { return; }
+ function closeIfApplicable(scope) {
+ if (evt && scope.getAutoClose() === 'disabled') {
+ return;
+ }
- var toggleElement = openScope.getToggleElement();
- if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
- return;
+ if (evt && evt.which === 3) {
+ return;
+ }
+
+ if (evt &&
+ isAnyTrigger(angular.element(evt.target))) {
+ return;
+ }
+ // could do "is contained in any trigger"; but doesn't seem needed yet
+
+ var toggleElement = scope.getToggleElement();
+ if (evt && toggleElement && containsNested(toggleElement, evt.target)) {
+ return;
+ }
+
+ var dropdownElement = scope.getDropdownElement();
+ if (evt &&
+ scope.getAutoClose() === 'outsideClick' &&
+ dropdownElement && containsNested(dropdownElement, evt.target)) {
+ return;
+ }
+ scope.isOpen = false;
+ scopesToApply.push(scope);
+
+ return true;
}
- var dropdownElement = openScope.getDropdownElement();
- if (evt && openScope.getAutoClose() === 'outsideClick' &&
- dropdownElement && dropdownElement[0].contains(evt.target)) {
- return;
+ if (openScope && openScope.isOpen) {
+ if (closeIfApplicable(openScope)) {
+ openScope.focusToggleElement();
+ }
}
- openScope.focusToggleElement();
- openScope.isOpen = false;
+ // close all the others too
+ const scopesToKeep = [];
+ oldOpenScopes.forEach(scope => {
+ if (!closeIfApplicable(scope)) {
+ scopesToKeep.push(scope);
+ }
+ });
+ oldOpenScopes.splice(0, oldOpenScopes.length, ...scopesToKeep);
+ // and apply
if (!$rootScope.$$phase) {
- openScope.$apply();
+ scopesToApply.forEach(s => s.$apply());
}
};