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:44 UTC
[brooklyn-ui] 02/15: add a new bit in the composer to show errors
and warnings
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 b51d3dc1312754dd4b1e61178684f78a7ef0a492
Author: Alex Heneveld <al...@cloudsoftcorp.com>
AuthorDate: Wed Apr 21 09:30:09 2021 +0100
add a new bit in the composer to show errors and warnings
can navigate quickly to entities with problems;
and have a place we can logically add "quick fixes"
---
.../catalog-selector/catalog-selector.less | 2 +-
.../providers/blueprint-service.provider.js | 57 +++++++-
.../app/views/main/graphical/graphical.state.html | 144 ++++++++++++++++++++-
.../app/views/main/graphical/graphical.state.js | 19 +++
.../app/views/main/graphical/graphical.state.less | 119 ++++++++++++++---
.../blueprint-composer/app/views/main/main.less | 6 +-
6 files changed, 321 insertions(+), 26 deletions(-)
diff --git a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
index 1a2ac11..a1e5a61 100644
--- a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
+++ b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
@@ -22,7 +22,7 @@ catalog-selector {
display: block;
margin-top: 15px;
- height: 100%;
+ //height: 100%; //makes it too big
.grid {
margin-bottom: 15px;
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 19794ab..7f78da1 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,
+ getAllIssues: getAllIssues,
populateEntityFromApi: populateEntityFromApiSuccess,
populateLocationFromApi: populateLocationFromApiSuccess,
addConfigKeyDefinition: addConfigKeyDefinition,
@@ -148,32 +149,74 @@ function BlueprintService($log, $q, $sce, paletteApi, iconGenerator, dslService)
return RESERVED_KEYS.indexOf(key) > -1;
}
- function getIssues(entity = blueprint) {
+ function getIssues(entity = blueprint, nonRecursive) {
let issues = [];
if (entity.hasIssues()) {
- issues = issues.concat(entity.issues.map((issue)=> {
+ issues = issues.concat(entity.issues.map((issue) => {
let newIssue = Issue.builder().message(issue.message).group(issue.group).ref(issue.ref).level(issue.level).build();
newIssue.entity = entity;
return newIssue;
}));
}
- entity.policies.forEach((policy)=> {
+ entity.policies.forEach((policy) => {
issues = issues.concat(getIssues(policy));
});
- entity.enrichers.forEach((enricher)=> {
+ entity.enrichers.forEach((enricher) => {
issues = issues.concat(getIssues(enricher));
});
- entity.children.forEach((child)=> {
- issues = issues.concat(getIssues(child));
- });
+ if (!nonRecursive) {
+ entity.children.forEach((child) => {
+ issues = issues.concat(getIssues(child));
+ });
+ }
return issues;
}
+ function getAllIssues(entity = blueprint) {
+ return collectAllIssues({}, entity);
+ }
+
+ function collectAllIssues(result, entity) {
+ if (!result.entities) {
+ result.entities = {};
+ result.byEntity = {};
+ result.count = 0;
+ result.errors = { byEntity: {}, count: 0 }
+ result.warnings = { byEntity: {}, count: 0 }
+ }
+ if (result.entities[entity._id]) {
+ // already visited, some sort of reference; ignore
+ } else {
+ result.entities[entity._id] = entity;
+
+ let issues = getIssues(entity, true);
+ if (issues.length) {
+ result.byEntity[entity._id] = issues;
+ result.count += issues.length;
+
+ let errors = issues.filter(i => i.level.id == 'error');
+ if (errors.length) {
+ result.errors.byEntity[entity._id] = errors;
+ result.errors.count += errors.length;
+ }
+
+ let warnings = issues.filter(i => i.level.id != 'error');
+ if (warnings.length) {
+ result.warnings.byEntity[entity._id] = warnings;
+ result.warnings.count += warnings.length;
+ }
+ }
+
+ entity.children.forEach((child)=> collectAllIssues(result, child));
+ }
+ return result;
+ }
+
function hasIssues() {
return this.getIssues().length > 0;
}
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 711d4c1..b6cf40e 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
@@ -22,15 +22,157 @@
<div class="layout">
<div class="toolbar">
<div class="list-group">
- <a href class="list-group-item"
+ <a class="hand list-group-item"
ng-repeat="section in vm.sections track by $index"
ng-class="{'active': vm.selectedSection === section}"
ng-click="vm.selectedSection = section">
<i class="fa fa-fw" ng-class="section.icon"></i>
</a>
+
+ <div class="spacer"></div>
+ <a class="hand list-group-item errors-square"
+ title="Click to show/hide details of errors"
+ ng-class="{ errorsActive: errorsPane.level }"
+ ng-show="allIssues.errors.count"
+ ng-click="errorsPane.level = errorsPane.level ? null : 'summary'">
+ <i class="fa fa-fw fa-times-circle"></i> {{ allIssues.errors.count }}
+ </a>
+ <a class="hand list-group-item warning-square"
+ title="Click to show/hide details of warnings"
+ ng-class="{ errorsActive: errorsPane.level }"
+ ng-show="!allIssues.errors.count && allIssues.warnings.count"
+ ng-click="errorsPane.level = errorsPane.level ? null : 'summary'">
+ <i class="fa fa-fw fa-exclamation-triangle"></i> {{ allIssues.warnings.count }}
+ </a>
</div>
</div>
+ <div ng-if="errorsPane.level == 'summary'" class="errors-popout">
+ <div class="errors-body">
+
+ <a ng-click="errorsPane.level = null" class="hand errors-close"><i class="fa fa-fw fa-times"></i></a>
+
+ <div ng-if="allIssues.errors.count">
+ <a class="hand" ng-click="errorsPane.level = 'error-errors'">
+ {{ allIssues.errors.count }}
+ <span ng-if="allIssues.errors.count == 1">error</span>
+ <span ng-if="allIssues.errors.count != 1">errors</span>
+ </a>
+ in
+ <a class="hand" ng-click="errorsPane.level = 'error-entities'">
+ {{ 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>
+ </a>
+ </div>
+
+ <!-- TODO add links and panels for warnings -->
+ <div ng-if="allIssues.warnings.count">
+ {{ allIssues.warnings.count }}
+ <span ng-if="allIssues.errors.count == 1">warning</span>
+ <span ng-if="allIssues.errors.count != 1">warnings</span>
+ in {{ vm.size(allIssues.warnings.byEntity) }}
+ <span ng-if="vm.size(allIssues.warnings.byEntity) == 1">entity</span>
+ <span ng-if="vm.size(allIssues.warnings.byEntity) != 1">entities</span>
+ </div>
+
+ <div ng-if="!allIssues.errors.count && !allIssues.warnings.count">
+ No errors or warnings.
+ </div>
+
+ </div>
+ </div>
+
+ <div ng-if="errorsPane.level == 'error-entities'" class="errors-popout">
+ <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>
+
+ with
+
+ <a class="hand" ng-click="errorsPane.level = 'error-errors'">
+ {{ allIssues.errors.count }}
+ <span ng-if="allIssues.errors.count == 1">error</span>
+ <span ng-if="allIssues.errors.count != 1">errors</span>
+ </a>
+ </div>
+
+ <div class="errors-body">
+ <div ng-repeat="(itemK,itemV) in allIssues.errors.byEntity" class="error-line">
+ <div class="error-line-marker">
+ <i class="fa fa-fw fa-times-circle"></i>
+ </div>
+ <div class="error-line-text">
+ {{ allIssues.entities[itemK].id ? allIssues.entities[itemK].id + ' ('+allIssues.entities[itemK].type+')'
+ : allIssues.entities[itemK].type }}:
+ {{ itemV.length }} {{ itemV.length==1 ? 'error' : 'errors' }}
+ </div>
+ <div class="error-line-action">
+ <a class="hand" ui-sref="main.graphical.edit.entity({entityId: itemK})">
+ <i class="fa fa-fw fa-external-link"></i>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div ng-if="errorsPane.level == 'error-errors'" class="errors-popout">
+ <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>
+ <span ng-show="allIssues.errors.count != vm.size(allIssues.errors.byMessage)"> ({{ vm.size(allIssues.errors.byMessage) }} unique)</span>
+
+ in
+
+ <a class="hand" ng-click="errorsPane.level = 'error-entities'">
+ {{ 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>
+ </a>
+ </div>
+
+ <div class="errors-body">
+ <div ng-repeat="(itemK,itemV) in allIssues.errors.byMessage" class="error-line">
+ <div class="error-line-marker">
+ <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">
+ <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 }}
+ </div>
+ <div class="error-line-action">
+ <a class="hand" ui-sref="main.graphical.edit.entity({entityId: issue.entity._id})">
+ <i class="fa fa-fw fa-external-link"></i>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!--
+ <div class="error-line-action">
+ <a class="hand" ui-sref="main.graphical.edit.entity({entityId: itemK})">
+ <i class="fa fa-fw fa-external-link"></i>
+ </a>
+ </div>
+ -->
+ </div>
+ </div>
+ </div>
+
<designer on-selection-change="vm.onCanvasSelection"></designer>
</div>
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 0e7e0b2..143ebb2 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
@@ -41,10 +41,29 @@ function graphicalController($scope, $state, $filter, blueprintService, paletteS
this.sections = paletteService.getSections();
this.selectedSection = Object.values(this.sections).find(section => section.type === EntityFamily.ENTITY);
$scope.paletteState = {}; // share state among all sections
+ $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);
this.onCanvasSelection = (item) => {
$scope.canvasSelectedItem = item;
}
+ this.size = (obj) => {
+ if (!obj) return 0;
+ return Object.keys(obj).length;
+ }
+
this.getOnSelectText = (selectableType) => $scope.canvasSelectedItem ? "Add to " + $filter('entityName')($scope.canvasSelectedItem) : "Add to application";
this.addSelectedTypeToTargetEntity = (selectedType, targetEntity) => {
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 915d352..eeada0b 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
@@ -28,6 +28,7 @@
.toolbar {
position: relative;
height: calc(~"100vh - 105px");
+ width: 48px; // palette pane has left: 49px
background-color: #fff;
overflow-y: scroll;
z-index: 40;
@@ -43,27 +44,113 @@
background-color: @navbar-default-border;
}
- .list-group-item {
- @active-border-width: 4px;
- padding-top: 10px + @active-border-width;
- padding-bottom: 9px + @active-border-width;
- border-left: none;
- border-radius: 0;
+ .list-group {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ margin-bottom: 0;
+
+ .list-group-item {
+ @active-border-width: 4px;
+ padding-top: 10px + @active-border-width;
+ padding-bottom: 9px + @active-border-width;
+ border-left: none;
+ border-radius: 0;
+
+ &:first-child {
+ border-top: none;
+ }
+ &.active {
+ border-right: none;
+ border-color: @list-group-border;
+ border-bottom: @active-border-width solid @brand-primary;
+ background-color: #fff;
+ color: @list-group-link-color;
+ padding-bottom: 10px;
+ }
+ }
- &:first-child {
- border-top: none;
+
+ .spacer {
+ flex: 1 1 auto;
+ }
+
+ .errors-square, .warning-square {
+ padding-left: 0;
+ padding-right: 0;
+ text-align: center;
+ &.errorsActive {
+ background: @gray-lighter;
+ }
}
- &.active {
- border-right: none;
- border-color: @list-group-border;
- border-bottom: @active-border-width solid @brand-primary;
- background-color: #fff;
- color: @list-group-link-color;
- padding-bottom: 10px;
+ .errors-square {
+ color: @brand-danger;
+ }
+ .warning-square {
+ color: @brand-warning;
+ }
+ }
+ }
+ .errors-popout {
+ position: absolute;
+ left: 49px;
+ width: 440px;
+ bottom: 0;
+ z-index: 60;
+ border-top: 1px solid @gray-lighter;
+ background: @gray-lightest;
+
+ .errors-close {
+ float: right;
+ }
+
+ .errors-body {
+ padding: 8px;
+ }
+
+ .errorsHeader {
+ width: 100%;
+ background: @gray-lighter;
+ border-top: 1px solid @gray-light;
+ border-bottom: 1px solid @gray-light;
+ padding: 8px;
+ margin-bottom: 8px;
+ }
+ max-height: 600px;
+ overflow-y: scroll;
+ }
+ .error-line {
+ display: flex;
+ justify-self: stretch;
+ padding-bottom: 8px;
+ .error-line-marker, .error-line-action {
+ flex: 0 0 auto;
+ margin-top: 4px;
+ }
+ .error-line-text {
+ flex: 1 1 auto;
+ }
+ .error-line-marker {
+ color: @brand-danger;
+ padding-right: 0.5ex;
+ }
+ .error-line-action {
+ padding-left: 0.5ex;
+ }
+ .error-line-sub-line {
+ display: flex;
+ justify-self: stretch;
+ margin-bottom: 4px;
+ margin-top: 4px;
+ .error-line-marker {
+ color: inherit;
+ font-size: 50%;
+ padding-right: 1em;
+ margin-top: 6px;
}
}
}
-
+
#palette-controls-button {
margin-right: -2px; // not sure why the default -1px doesn't work to merge the borders?
}
diff --git a/ui-modules/blueprint-composer/app/views/main/main.less b/ui-modules/blueprint-composer/app/views/main/main.less
index 06e390b..8e38515 100644
--- a/ui-modules/blueprint-composer/app/views/main/main.less
+++ b/ui-modules/blueprint-composer/app/views/main/main.less
@@ -111,4 +111,8 @@
.flex {
display: flex;
-}
\ No newline at end of file
+}
+
+.hand {
+ cursor: pointer;
+}