Repository: brooklyn-ui Updated Branches: refs/heads/master 60ddc6213 -> a9fcd5276
catalog saver to support 'application', 'template', and 'entity' in line with https://github.com/apache/brooklyn-server/pull/1015 also when using a template the metadata is not populated as the intention is probably not to overwrite, unless the user selects to save it as a template Project: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/commit/a552f046 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/tree/a552f046 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/diff/a552f046 Branch: refs/heads/master Commit: a552f04640459669cabdd5882b9e8964998b746e Parents: 4093d1f Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Authored: Fri Nov 16 13:42:17 2018 +0000 Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Committed: Fri Nov 16 13:43:29 2018 +0000 ---------------------------------------------------------------------- .../catalog-saver/catalog-saver.directive.js | 121 +++++++++++++------ .../catalog-saver.modal.template.html | 26 ++-- .../app/views/main/main.controller.js | 25 +++- 3 files changed, 124 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/a552f046/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js ---------------------------------------------------------------------- diff --git a/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js b/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js index 252f6da..adc3f42 100644 --- a/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js +++ b/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js @@ -42,14 +42,15 @@ const TYPES = [ ]; angular.module(MODULE_NAME, [angularAnimate, uibModal, brUtils]) - .directive('catalogSaver', ['$rootScope', '$uibModal', '$injector', 'composerOverrides', 'blueprintService', saveToCatalogModalDirective]) + .directive('catalogSaver', ['$rootScope', '$uibModal', '$injector', '$filter', 'composerOverrides', 'blueprintService', saveToCatalogModalDirective]) .directive('catalogVersion', ['$parse', catalogVersionDirective]) + .directive('blueprintNameOrSymbolicNameAndBundleIdRequired', blueprintNameOrSymbolicNameAndBundleIdRequiredDirective) .filter('bundlize', bundlizeProvider) .run(['$templateCache', templateCache]); export default MODULE_NAME; -export function saveToCatalogModalDirective($rootScope, $uibModal, $injector, composerOverrides, blueprintService) { +export function saveToCatalogModalDirective($rootScope, $uibModal, $injector, $filter, composerOverrides, blueprintService) { return { restrict: 'E', templateUrl: function (tElement, tAttrs) { @@ -62,38 +63,50 @@ export function saveToCatalogModalDirective($rootScope, $uibModal, $injector, co }; function link($scope, $element) { - $scope.buttonText = $scope.config.label || ($scope.config.itemType ? `Update ${$scope.config.name || $scope.config.symbolicName}` : 'Add to catalog'); - + if (!$scope.config.original) { + // original if provided contains the original metadata, e.g. for use if coming from a template and switching between template and non-template + $scope.config.original = {} + } + $scope.isNewFromTemplate = () => ($scope.config.itemType !== 'template' && $scope.config.original.itemType === 'template'); + $scope.isUpdate = () => !$scope.isNewFromTemplate() && Object.keys($scope.config.original).length>0; + $scope.buttonTextFn = () => $scope.config.label || ($scope.isUpdate() && ($scope.config.name || $scope.config.original.name || $scope.config.symbolicName || $scope.config.original.symbolicName)) || 'Add to catalog'; + $scope.buttonText = $scope.buttonTextFn(); + $scope.activateModal = () => { let entity = blueprintService.get(); let metadata = blueprintService.entityHasMetadata(entity) ? blueprintService.getEntityMetadata(entity) : new Map(); - - // Reset the config values if this is not an update - $scope.isUpdate = Object.keys($scope.config).length > ($scope.config.label ? 1 : 0); - if (!$scope.isUpdate) { - $scope.config.itemType = 'template'; - } - - // Set various properties from the blueprint entity data - if (!$scope.config.version && (entity.hasVersion() || metadata.has('version'))) { - $scope.config.version = entity.version || metadata.get('version'); + + if (!$scope.config.itemType) { + // This is the default item type + $scope.config.itemType = 'application'; } + + // Set various properties from the blueprint entity data if not already set if (!$scope.config.iconUrl && (entity.hasIcon() || metadata.has('iconUrl'))) { $scope.config.iconUrl = entity.icon || metadata.get('iconUrl'); } - if (!$scope.config.name && entity.hasName()) { - $scope.config.name = entity.name; - } - if (!$scope.config.symbolicName && (entity.hasId() || metadata.has('id'))) { - $scope.config.symbolicName = entity.id || metadata.get('id'); - } - if (!$scope.config.bundle) { - // NB: when editing a bundle this will already be set - if ($scope.config.symbolicName) { - $scope.config.bundle = $scope.config.symbolicName; + if (!$scope.isNewFromTemplate()) { + // (these should only be set if not making something new from a template, as the entity items will refer to the template) + + // the name and the ID can be set in the UI, + // or all can be inherited if root node is a known application type we are editting + // (normally in those cases $scope.config will already be set by caller, but maybe not always) + if (!$scope.config.name && entity.hasName()) { + $scope.config.name = entity.name; + } + if (!$scope.config.symbolicName && (entity.hasId() || metadata.has('id'))) { + $scope.config.symbolicName = entity.id || metadata.get('id'); + } + if (!$scope.config.version && (entity.hasVersion() || metadata.has('version'))) { + $scope.config.version = entity.version || metadata.get('version'); + } + if (!$scope.config.bundle) { + if ($scope.config.symbolicName) { + $scope.config.bundle = $scope.config.symbolicName; + } } } - + // Override this callback to update configuration data elsewhere $scope.config = (composerOverrides.updateCatalogConfig || ((config, $element) => config))($scope.config, $element); @@ -103,7 +116,7 @@ export function saveToCatalogModalDirective($rootScope, $uibModal, $injector, co controller: ['$scope', '$filter', 'blueprintService', 'paletteApi', 'brUtilsGeneral', CatalogItemModalController], scope: $scope, }); - + // Promise is resolved when the modal is closed. We expect the modal to pass back the action to perform thereafter modalInstance.result.then(reason => { switch (reason) { @@ -133,11 +146,12 @@ export function CatalogItemModalController($scope, $filter, blueprintService, pa $scope.getTitle = () => { switch ($scope.state.view) { case VIEWS.form: - return $scope.isUpdate ? `Update ${$scope.config.name || $scope.config.symbolicName || 'blueprint'}` : 'Add to catalog'; + return $scope.isUpdate() ? `Update ${$scope.config.name || $scope.config.symbolicName || 'blueprint'}` : 'Add to catalog'; case VIEWS.saved: - return `${$scope.config.name || $scope.config.symbolicName || 'Blueprint'} ${$scope.isUpdate ? 'updated' : 'saved'}`; + return `${$scope.config.name || $scope.config.symbolicName || 'Blueprint'} ${$scope.isUpdate() ? 'updated' : 'saved'}`; } }; + $scope.title = $scope.getTitle(); $scope.save = () => { $scope.state.saving = true; @@ -160,10 +174,10 @@ export function CatalogItemModalController($scope, $filter, blueprintService, pa function createBom() { let blueprint = blueprintService.getAsJson(); - let bundleBase = $scope.config.bundle || $filter('bundlize')($scope.config.name); - let bundleId = $scope.config.symbolicName || $filter('bundlize')($scope.config.name); + let bundleBase = $scope.config.bundle || $scope.defaultBundle; + let bundleId = $scope.config.symbolicName || $scope.defaultSymbolicName; if (!bundleBase || !bundleId) { - throw "Either display name must be set of bundle and symbolic name explicitly set"; + throw "Either the display name must be set, or the bundle and symbolic name must be explicitly set"; } let bomItem = { @@ -176,8 +190,9 @@ export function CatalogItemModalController($scope, $filter, blueprintService, pa version: $scope.config.version, items: [ bomItem ] }; - if (brUtilsGeneral.isNonEmpty($scope.config.name)) { - bomItem.name = $scope.config.name; + let bundleName = $scope.config.name || $scope.defaultName; + if (brUtilsGeneral.isNonEmpty(bundleName)) { + bomItem.name = bundleName; } if (brUtilsGeneral.isNonEmpty($scope.config.description)) { bomItem.description = $scope.config.description; @@ -188,6 +203,44 @@ export function CatalogItemModalController($scope, $filter, blueprintService, pa return jsYaml.dump({ 'brooklyn.catalog': bomCatalogYaml }); } + + let bundlize = $filter('bundlize'); + $scope.updateDefaults = (newName) => { + $scope.defaultName = ($scope.config.itemType==='template' && $scope.config.original.name) || null; + $scope.defaultSymbolicName = ($scope.config.itemType==='template' && $scope.config.original.symbolicName) || bundlize(newName) || null; + $scope.defaultBundle = ($scope.config.itemType==='template' && $scope.config.original.bundle) || bundlize(newName) || null; + }; + $scope.$watchGroup(['config.name', 'config.itemType'], (newVals) => { + $scope.updateDefaults(newVals[0]); + $scope.form.name.$validate(); + $scope.buttonText = $scope.buttonTextFn(); + $scope.title = $scope.getTitle(); + }); +} + +function blueprintNameOrSymbolicNameAndBundleIdRequiredDirective() { + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attr, ngModel) { + ngModel.$validators.blueprintNameOrSymbolicNameAndBundleIdRequired = function(modelValue, viewValue) { + scope.updateDefaults(modelValue); + if (!ngModel.$isEmpty(modelValue)) { + // anything set is valid + return true; + } + // if not set, we need a bundle and symbolic name + if (scope.config.bundle && scope.config.symbolicName) { + return true; + } + // or if we have defaults for bundle and symbolic name we don't need this name + if (scope.defaultBundle && scope.defaultSymbolicName) { + return true; + } + return false; + } + }, + }; } export function catalogVersionDirective($parse) { @@ -220,7 +273,7 @@ export function catalogVersionDirective($parse) { ctrl.$validators.exist = (modelValue, viewValue) => { return !angular.isDefined(matches) || ctrl.$isEmpty(viewValue) || viewValue.endsWith('SNAPSHOT') || force === true || matches.indexOf(viewValue) === -1; - }; + }; } } http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/a552f046/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html ---------------------------------------------------------------------- diff --git a/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html b/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html index c3a6cb7..4b5f626 100644 --- a/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html +++ b/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.modal.template.html @@ -26,9 +26,9 @@ <div class="form-group" ng-class="{'has-error': form.name.$invalid}"> <label class="control-label">Blueprint display name</label> - <input ng-model="config.name" ng-disabled="state.saving" class="form-control" name="name" type="text" required /> + <input ng-model="config.name" ng-disabled="state.saving" class="form-control" name="name" type="text" placeholder="{{ defaultName }}" blueprint-name-or-symbolic-name-and-bundle-id-required/> <p class="help-block" ng-show="form.name.$invalid"> - <span ng-if="form.name.$error.required">You must specify a name for this item</span> + <span ng-if="form.name.$invalid">You must specify a name for this item or supply explicit bundle ID and blueprint symbolic name</span> </p> </div> @@ -60,7 +60,7 @@ <label class="control-label">Bundle ID</label> <div class="input-group"> <span class="input-group-addon">catalog-bom-</span> - <input ng-model="config.bundle" ng-disabled="state.saving" class="form-control" name="bundle" ng-pattern="state.pattern" autofocus placeholder="{{ (config.name | bundlize) || 'E.g. my-bundle-id' }}"/> + <input ng-model="config.bundle" ng-disabled="state.saving" class="form-control" name="bundle" ng-pattern="state.pattern" autofocus placeholder="{{ defaultBundle || 'E.g. my-bundle-id' }}"/> </div> <p class="help-block" ng-show="form.bundle.$invalid"> <span ng-if="form.bundle.$error.pattern">The bundle ID can contains only letters, numbers as well a the following characters: <code>.</code>, <code>-</code> and <code>_</code></span> @@ -69,7 +69,7 @@ <div class="form-group" ng-class="{'has-error': form.symbolicName.$invalid}"> <label class="control-label">Blueprint symbolic name</label> - <input ng-model="config.symbolicName" ng-disabled="state.saving" class="form-control" name="symbolicName" ng-pattern="state.pattern" autofocus placeholder="{{ (config.name | bundlize) || 'E.g. my-catalog-id' }}"/> + <input ng-model="config.symbolicName" ng-disabled="state.saving" class="form-control" name="symbolicName" ng-pattern="state.pattern" autofocus placeholder="{{ defaultSymbolicName || 'E.g. my-catalog-id' }}"/> <p class="help-block" ng-show="form.symbolicName.$invalid"> <span ng-if="form.symbolicName.$error.pattern">The blueprint symbolic name can contains only letters, numbers as well a the following characters: <code>.</code>, <code>-</code> and <code>_</code></span> </p> @@ -79,24 +79,32 @@ <label class="control-label">Blueprint type</label> <div class="checkbox"> <label> + <input ng-model="config.itemType" ng-disabled="state.saving" name="itemType" type="radio" value="application" /> + Application entity + <i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'" + uib-popover="Save as an application entity which can be configured and used in blueprints or used on its own ('application' item type)"></i> + </label> + </div> + <div class="checkbox"> + <label> <input ng-model="config.itemType" ng-disabled="state.saving" name="itemType" type="radio" value="template" /> - Template + Application template <i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'" - uib-popover="Save as 'template' which can be used as an editable starting point for other blueprints or quick-launched directly"></i> + uib-popover="Save as a blueprint template which can be used as an editable starting point for blueprints or used as an application entity ('template' item type)"></i> </label> </div> <div class="checkbox"> <label> <input ng-model="config.itemType" ng-disabled="state.saving" name="itemType" type="radio" value="entity" /> - Entity + Extended entity <i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'" - uib-popover="Save as a new 'entity' type which can be used in blueprints"></i> + uib-popover="Save as an entity which can be configured and used in blueprints, exposing the config and adjuncts it inherits ('entity' item type)"></i> </label> </div> </div> <div class="form-group"> - <label class="control-label">Blueprint icon url</label> + <label class="control-label">Blueprint icon URL</label> <input ng-model="config.iconUrl" ng-disabled="state.saving" class="form-control" name="iconUrl" type="text" /> </div> </br-collapsible> http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/a552f046/ui-modules/blueprint-composer/app/views/main/main.controller.js ---------------------------------------------------------------------- diff --git a/ui-modules/blueprint-composer/app/views/main/main.controller.js b/ui-modules/blueprint-composer/app/views/main/main.controller.js index d4204f8..bd0180f 100644 --- a/ui-modules/blueprint-composer/app/views/main/main.controller.js +++ b/ui-modules/blueprint-composer/app/views/main/main.controller.js @@ -105,16 +105,31 @@ export function MainController($scope, $element, $log, $state, $stateParams, brB vm.saveToCatalogConfig = {}; if (edit) { + console.log("edit", edit); vm.saveToCatalogConfig = Object.assign(vm.saveToCatalogConfig, { - bundle: edit.bundle.symbolicName.replace(/^catalog-bom-/, ''), version: edit.type.version, - symbolicName: edit.type.symbolicName, - itemType: edit.type.template ? 'template' : 'entity', - name: edit.type.displayName, + template: edit.type.template || false, + itemType: edit.type.template || !edit.type.superTypes || edit.type.superTypes.contains("org.apache.brooklyn.api.entity.Application") + ? 'application' + : 'entity', description: edit.type.description, iconUrl: edit.type.iconUrl, - versions: edit.versions, + original: { + bundle: edit.bundle.symbolicName.replace(/^catalog-bom-/, ''), + symbolicName: edit.type.symbolicName, + name: edit.type.displayName, + versions: edit.versions, + itemType: edit.type.template ? 'template' + : !edit.type.superTypes || edit.type.superTypes.contains("org.apache.brooklyn.api.entity.Application") ? 'application' + : 'entity', + } }); + if (!edit.type.template) { + // if loading a templates, do NOT set name, bundle, symbolicName, versions + // or in other words, for other items, DO set these + vm.saveToCatalogConfig = Object.assign(vm.saveToCatalogConfig, vm.saveToCatalogConfig.original); + } + yaml = edit.type.plan.data; }