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 <[email protected]> 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()); } };
