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