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
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 @@
];
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 @@
};
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';
+
+ if (!$scope.config.itemType) {
+ // This is the default item type
+ $scope.config.itemType = 'application';
}
-
- // 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');
- }
+
+ // 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 @@
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 @@
$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 @@
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 @@
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 @@
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 @@
ctrl.$validators.exist = (modelValue, viewValue) => {
return !angular.isDefined(matches) || ctrl.$isEmpty(viewValue) || viewValue.endsWith('SNAPSHOT') || force === true || matches.indexOf(viewValue) === -1;
- };
+ };
}
}
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="template" />
- Template
+ <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 'template' which can be used as an editable starting point for other blueprints or quick-launched directly"></i>
+ 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" />
+ Application template
+ <i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'"
+ 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>
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 @@
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;
}