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 2021/04/27 15:12:47 UTC
[brooklyn-ui] 05/15: add quick fix support
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 b1c9f0766482d83e0f0739af38bddb5db33b945d
Author: Alex Heneveld <al...@cloudsoftcorp.com>
AuthorDate: Thu Apr 22 22:12:51 2021 +0100
add quick fix support
in global view only; on entity coming soon
---
docs/basic-with-constraint.bom | 6 ++
.../providers/blueprint-service.provider.js | 11 +-
.../app/components/quick-fix/quick-fix.html | 21 ++++
.../app/components/quick-fix/quick-fix.js | 120 +++++++++++++++++++++
.../app/components/quick-fix/quick-fix.less | 21 ++++
.../app/components/util/model/issue.model.js | 14 +++
ui-modules/blueprint-composer/app/index.js | 3 +-
.../app/views/main/graphical/graphical.state.html | 61 ++++++++---
.../app/views/main/graphical/graphical.state.js | 25 +++--
.../app/views/main/graphical/graphical.state.less | 4 +
10 files changed, 259 insertions(+), 27 deletions(-)
diff --git a/docs/basic-with-constraint.bom b/docs/basic-with-constraint.bom
index 08ed2c6..b3f440a 100644
--- a/docs/basic-with-constraint.bom
+++ b/docs/basic-with-constraint.bom
@@ -27,4 +27,10 @@ brooklyn.catalog:
- name: zip_code
constraints:
- forbiddenIf: post_code
+ tags:
+ - ui-composer-hints:
+ config-quick-fixes:
+ - key: zip_code
+ fix: clear_config
+ message-regex: .*cannot both be set.*
diff --git a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
index 1639577..c98cf69 100644
--- a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
@@ -65,6 +65,7 @@ function BlueprintService($log, $q, $sce, paletteApi, iconGenerator, dslService)
isReservedKey: isReservedKey,
getIssues: getIssues,
hasIssues: hasIssues,
+ clearAllIssues: clearAllIssues,
getAllIssues: getAllIssues,
populateEntityFromApi: populateEntityFromApiSuccess,
populateLocationFromApi: populateLocationFromApiSuccess,
@@ -177,6 +178,12 @@ function BlueprintService($log, $q, $sce, paletteApi, iconGenerator, dslService)
return issues;
}
+ // typically followed by a call to refresh
+ function clearAllIssues(entity = blueprint) {
+ entity.resetIssues();
+ entity.children.forEach(clearAllIssues);
+ }
+
function getAllIssues(entity = blueprint) {
return collectAllIssues({}, entity);
}
@@ -559,9 +566,9 @@ function BlueprintService($log, $q, $sce, paletteApi, iconGenerator, dslService)
return promises;
}, [])).then(results => {
results.forEach(result => {
- entity.clearIssues({ref: result.key});
+ entity.clearIssues({ref: result.key, phase: 'relationship'});
result.issues.forEach(issue => {
- entity.addIssue(Issue.builder().group('config').ref(result.key).message($sce.trustAsHtml(issue)).build());
+ entity.addIssue(Issue.builder().group('config').phase('relationship').ref(result.key).message($sce.trustAsHtml(issue)).build());
});
})
});
diff --git a/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.html b/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.html
new file mode 100644
index 0000000..e9bdb2e
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.html
@@ -0,0 +1,21 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<span class="quick-fix">
+ bob {{ foo }}.
+</span>
diff --git a/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.js b/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.js
new file mode 100644
index 0000000..184de2c
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.js
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import angular from 'angular';
+import template from './quick-fix.html';
+
+const MODULE_NAME = 'brooklyn.components.quick-fix.quick-fix';
+
+angular.module(MODULE_NAME, [])
+ .directive('quickFix', ['$rootScope', quickFixDirective]);
+
+export default MODULE_NAME;
+
+export function quickFixDirective($rootScope) {
+ return {
+ restrict: 'E',
+ template: template,
+ scope: {
+ issues: '=',
+ },
+ link: link,
+ };
+
+ function link(scope, element, attrs, specEditor) {
+ console.log("quick-fix", attrs);
+ scope.foo = "hello";
+ }
+}
+
+export function computeQuickFixes(allIssues) {
+ if (!allIssues) allIssues = {};
+ if (!allIssues.errors) allIssues.errors = {};
+
+ allIssues.errors.byMessage = {};
+ Object.values(allIssues.errors.byEntity).forEach(list => {
+ list.forEach(issue => {
+ // TODO key should be a tuple of group, ref, message
+ let key = issue.group+":"+issue.ref+":"+issue.message;
+ let v = allIssues.errors.byMessage[key];
+ if (!v) {
+ v = allIssues.errors.byMessage[key] = {
+ group: issue.group,
+ ref: issue.ref,
+ message: issue.message,
+ issues: [],
+ quickFixes: {},
+ };
+ }
+
+ let issueO = {
+ issue,
+ //quickFixes: {},
+ }
+ v.issues.push(issueO);
+
+ let qfs = getQuickFixHintsForIssue(issue);
+ (qfs || []).forEach(qf => {
+ let qfi = getQuickFixProposer(qf['fix']);
+ if (!qfi) {
+ console.log("Skipping unknown quick fix", qf);
+ } else {
+ qfi.propose(issue, v.quickFixes);
+ // we could offer the fix per-issue, but no need as they can get that by navigating to the entity
+ //qfi.propose(issue, issueO.quickFixes);
+ }
+ });
+ });
+ });
+ return allIssues;
+}
+
+const QUICK_FIX_PROPOSERS = {
+ clear_config: {
+ // the propose function updates the proposals object
+ propose: (issue, proposals) => {
+ if (!issue.ref) return;
+
+ if (!proposals) proposals = {};
+ if (!proposals.clear_config) {
+ proposals.clear_config = {
+ text: "Remove the current value (clear config \""+issue.ref+"\")",
+ apply: (issue) => issue.entity.removeConfig(issue.ref),
+ issues: [],
+ };
+ }
+ proposals.clear_config.issues.push(issue);
+ },
+ }
+};
+
+export function getQuickFixProposer(type) {
+ return QUICK_FIX_PROPOSERS[type];
+}
+
+export function getQuickFixHintsForIssue(issue) {
+ if (issue.group === 'config') {
+ let hints = (issue.entity.miscData.get('ui-composer-hints') || {})['config-quick-fixes'] || [];
+ hints = hints.filter(h => h.key === issue.ref);
+ if (!hints.length) return null;
+ hints = hints.filter(h => !h['message-regex'] || new RegExp(h['message-regex']).test(issue.message));
+ return hints;
+ }
+ return null;
+}
\ No newline at end of file
diff --git a/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.less b/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.less
new file mode 100644
index 0000000..77412b2
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/quick-fix/quick-fix.less
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+quick-fix, .quick-fix {
+}
diff --git a/ui-modules/blueprint-composer/app/components/util/model/issue.model.js b/ui-modules/blueprint-composer/app/components/util/model/issue.model.js
index badfab5..f00bd7d 100644
--- a/ui-modules/blueprint-composer/app/components/util/model/issue.model.js
+++ b/ui-modules/blueprint-composer/app/components/util/model/issue.model.js
@@ -18,6 +18,7 @@
*/
const MESSAGE = new WeakMap();
const GROUP = new WeakMap();
+const PHASE = new WeakMap();
const REF = new WeakMap();
const LEVEL = new WeakMap();
@@ -36,6 +37,7 @@ export class Issue {
constructor() {
MESSAGE.set(this, '');
GROUP.set(this, '');
+ PHASE.set(this, '');
REF.set(this, '');
LEVEL.set(this, ISSUE_LEVEL.ERROR);
}
@@ -56,6 +58,14 @@ export class Issue {
return GROUP.get(this);
}
+ set phase(group) {
+ PHASE.set(this, group);
+ }
+
+ get phase() {
+ return PHASE.get(this);
+ }
+
set ref(ref) {
REF.set(this, ref);
}
@@ -92,6 +102,10 @@ class Builder {
return this;
}
+ phase(phase) {
+ this.issue.phase = phase;
+ }
+
ref(ref) {
this.issue.ref = ref;
return this;
diff --git a/ui-modules/blueprint-composer/app/index.js b/ui-modules/blueprint-composer/app/index.js
index 7504627..944fbad 100755
--- a/ui-modules/blueprint-composer/app/index.js
+++ b/ui-modules/blueprint-composer/app/index.js
@@ -59,6 +59,7 @@ import paletteDragAndDropService from "./components/providers/palette-dragndrop.
import actionService from "./components/providers/action-service.provider";
import tabService from "./components/providers/tab-service.provider";
import composerOverrides from "./components/providers/composer-overrides.provider";
+import quickFix from "./components/quick-fix/quick-fix";
import {mainState} from "./views/main/main.controller";
import {yamlAutodetectState, yamlCampState, yamlState} from "./views/main/yaml/yaml.state";
import {graphicalState} from "./views/main/graphical/graphical.state";
@@ -82,7 +83,7 @@ angular.module('brooklynBlueprintComposer', [ngAnimate, ngResource, ngCookies, n
brServerStatus, brAutoFocus, brIconGenerator, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement,
brYamlEditor, brUtils, brSpecEditor, brooklynCatalogSaver, brooklynApi, bottomSheet, stackViewer, brDragndrop, mdHelper,
customActionDirective, customConfigSuggestionDropdown, paletteApiProvider, paletteServiceProvider, blueprintLoaderApiProvider,
- breadcrumbs, catalogSelector, designer, objectCache, entityFilters, locationFilter, actionService, tabService, composerOverrides, blueprintService,
+ breadcrumbs, catalogSelector, designer, objectCache, entityFilters, locationFilter, actionService, tabService, composerOverrides, quickFix, blueprintService,
dslService, paletteDragAndDropService, recentlyUsedService, scriptTagDecorator, brandAngularJs])
.filter('dslParamLabel', ['$filter', dslParamLabelFilter])
.config(['$urlRouterProvider', '$stateProvider', '$logProvider', '$compileProvider', applicationConfig])
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
index b6cf40e..dba01b1 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
@@ -87,9 +87,9 @@
<div class="errorsHeader">
<a ng-click="errorsPane.level = null" class="hand errors-close"><i class="fa fa-fw fa-times"></i></a>
- {{ vm.size(allIssues.errors.byEntity) }}
- <span ng-if="vm.size(allIssues.errors.byEntity) == 1">entity</span>
- <span ng-if="vm.size(allIssues.errors.byEntity) != 1">entities</span>
+ <b>{{ vm.size(allIssues.errors.byEntity) }}
+ <span ng-if="vm.size(allIssues.errors.byEntity) == 1">entity</span>
+ <span ng-if="vm.size(allIssues.errors.byEntity) != 1">entities</span></b>
with
@@ -123,9 +123,9 @@
<div class="errorsHeader">
<a ng-click="errorsPane.level = null" class="hand errors-close"><i class="fa fa-fw fa-times"></i></a>
- {{ allIssues.errors.count }}
- <span ng-if="allIssues.errors.count == 1">error</span>
- <span ng-if="allIssues.errors.count != 1">errors</span>
+ <b>{{ allIssues.errors.count }}
+ <span ng-if="allIssues.errors.count == 1">error</span>
+ <span ng-if="allIssues.errors.count != 1">errors</span></b>
<span ng-show="allIssues.errors.count != vm.size(allIssues.errors.byMessage)"> ({{ vm.size(allIssues.errors.byMessage) }} unique)</span>
in
@@ -143,24 +143,59 @@
<i class="fa fa-fw fa-times-circle"></i>
</div>
<div class="error-line-text">
- {{ itemK }}:
- <a class="hand" ng-click="errorsPane.focus = (errorsPane.focus == itemK ? null : itemK)">{{ itemV.length }} {{ itemV.length==1 ? 'error' : 'errors' }}</a>
- <div class="error-line-sub" ng-if="errorsPane.focus == itemK">
- <div ng-repeat="issue in itemV" class="error-line-sub-line">
+ <span>
+ <span ng-show="vm.messageNeedsPrefix(itemV)">
+ {{ itemV.group }} {{ itemV.ref }}<span ng-show="itemV.message">:</span>
+ </span>
+ <span ng-show="itemV.message">
+ <ng-bind-html ng-bind-html="itemV.message"></ng-bind-html>
+ </span>
+ </span>
+
+ (<a class="hand" ng-click="errorsPane.focus = (errorsPane.focus == 'errors:'+itemK ? null : 'errors:'+itemK)"
+ ng-class="{active: errorsPane.focus == 'errors:'+itemK }">{{
+ itemV.issues.length }}
+ {{ itemV.issues.length==1 ? 'entity' : 'entities'
+ }}</a><span ng-if="vm.size(itemV.quickFixes)">;
+ <a class="hand" ng-click="errorsPane.focus = (errorsPane.focus == 'fixes:'+itemK ? null : 'fixes:'+itemK)"
+ ng-class="{active: errorsPane.focus == 'fixes:'+itemK }">
+ {{ vm.size(itemV.quickFixes) }} quick fix<span ng-if="vm.size(itemV.quickFixes) != 1">es</span> available</a></span>)
+
+ <div class="error-line-sub" ng-if="errorsPane.focus == 'errors:'+itemK">
+ <div ng-repeat="issue in itemV.issues" class="error-line-sub-line">
<div class="error-line-marker">
<i class="fa fa-fw fa-circle"></i>
</div>
<div class="error-line-text">
- {{ issue.entity.id ? issue.entity.id + ' ('+issue.entity.type+')'
- : issue.entity.type }}
+ {{ issue.issue.entity.id ? issue.issue.entity.id + ' ('+issue.issue.entity.type+')'
+ : issue.issue.entity.type }}
+ <!-- could offer the issue-specific quick fixes; but clearer to navigate to entity and do there -->
</div>
<div class="error-line-action">
- <a class="hand" ui-sref="main.graphical.edit.entity({entityId: issue.entity._id})">
+ <a class="hand" ui-sref="main.graphical.edit.entity({entityId: issue.issue.entity._id})">
<i class="fa fa-fw fa-external-link"></i>
</a>
</div>
</div>
</div>
+
+ <div class="error-line-sub" ng-if="errorsPane.focus == 'fixes:'+itemK">
+ <div ng-repeat="fix in itemV.quickFixes" class="error-line-sub-line">
+ <div class="error-line-marker">
+ <i class="fa fa-fw fa-magic"></i>
+ </div>
+ <div class="error-line-text">
+ {{ fix.text }}
+ <a class="hand btn btn-xs btn-primary" style="float: right;" ng-click="vm.applyQuickFix(fix)"
+ >Apply
+ ({{ vm.size(fix.issues) }}
+ <span ng-if="vm.size(fix.issues) == 1">entity</span
+ ><span ng-if="vm.size(fix.issues) != 1">entities</span
+ >)
+ </a>
+ </div>
+ </div>
+ </div>
</div>
<!--
<div class="error-line-action">
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
index 143ebb2..16cf7f8 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
@@ -19,6 +19,7 @@
import {graphicalEditEntityState} from './edit/entity/edit.entity.controller';
import {graphicalEditPolicyState} from './edit/policy/edit.policy.controller';
import {graphicalEditEnricherState} from './edit/enricher/edit.enricher.controller';
+import {computeQuickFixes} from '../../../components/quick-fix/quick-fix';
import {Entity, EntityFamily} from '../../../components/util/model/entity.model';
import template from './graphical.state.html';
@@ -36,6 +37,7 @@ export const graphicalState = {
};
function graphicalController($scope, $state, $filter, blueprintService, paletteService) {
+ let vm = this;
this.EntityFamily = EntityFamily;
this.sections = paletteService.getSections();
@@ -44,18 +46,11 @@ function graphicalController($scope, $state, $filter, blueprintService, paletteS
$scope.errorsPane = { level: null };
$scope.blueprint = blueprintService.get();
- $scope.$watch('blueprint', ()=> {
- $scope.allIssues = blueprintService.getAllIssues();
- $scope.allIssues.errors.byMessage = {};
- Object.values($scope.allIssues.errors.byEntity).forEach(list => {
- list.forEach(issue => {
- $scope.allIssues.errors.byMessage[issue.group+":"+issue.ref] = $scope.allIssues.errors.byMessage[issue.group+" "+issue.ref] || [];
- $scope.allIssues.errors.byMessage[issue.group+":"+issue.ref].push(issue);
- });
- });
- //console.log("blueprint update, get all issues", $scope.allIssues);
- }, true);
+ $scope.$watch('blueprint', () => vm.computeIssues(), true);
+ this.computeIssues = () => {
+ $scope.allIssues = computeQuickFixes(blueprintService.getAllIssues());
+ }
this.onCanvasSelection = (item) => {
$scope.canvasSelectedItem = item;
}
@@ -64,6 +59,7 @@ function graphicalController($scope, $state, $filter, blueprintService, paletteS
return Object.keys(obj).length;
}
+ this.messageNeedsPrefix = (itemV) => !itemV.message || (""+itemV.message).indexOf(itemV.ref)<0;
this.getOnSelectText = (selectableType) => $scope.canvasSelectedItem ? "Add to " + $filter('entityName')($scope.canvasSelectedItem) : "Add to application";
this.addSelectedTypeToTargetEntity = (selectedType, targetEntity) => {
@@ -96,4 +92,11 @@ function graphicalController($scope, $state, $filter, blueprintService, paletteS
$state.go(graphicalEditEntityState, {entityId: targetEntity._id});
}
};
+
+ this.applyQuickFix = (fix) => {
+ fix.issues.forEach(issue => fix.apply(issue));
+ // recompute errors
+ blueprintService.clearAllIssues();
+ blueprintService.refreshBlueprintMetadata()
+ }
}
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less
index eeada0b..3cb5fc1 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.less
@@ -118,6 +118,10 @@
}
max-height: 600px;
overflow-y: scroll;
+
+ a.active {
+ font-weight: bold;
+ }
}
.error-line {
display: flex;