in ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js [188:414]
export function CatalogItemModalController($scope, $filter, blueprintService, paletteApi, brUtilsGeneral) {
$scope.REASONS = REASONS;
$scope.VIEWS = VIEWS;
$scope.TYPES = TYPES;
$scope.state = {
pattern: VALID_FIELD_PATTERN,
view: VIEWS.form,
saving: false,
force: false
};
/* Derived properties & calculators, will be updated whenever $scope.state.view changes */
$scope.getTitle = () => {
// expect we should always have current or default name, possibly don't need symbolicName or `blueprint` defaults
const name = $scope.config.current.name || $scope.config.local.default.name || $scope.config.current.symbolicName || $scope.config.local.default.symbolicName;
switch ($scope.state.view) {
case VIEWS.form:
return $scope.isUpdate() ? `Update ${name || 'blueprint'}` : 'Add to catalog';
case VIEWS.saved:
return `${name || 'Blueprint'} ${$scope.isUpdate() ? 'updated' : 'saved'}`;
}
};
$scope.getCatalogURL = () => {
const urlPartVersion = _.get($scope, 'config.current.version') || _.get($scope, 'config.version');
if (!urlPartVersion) return "";
switch ($scope.state.view) {
case VIEWS.form:
return '';
case VIEWS.saved:
// TODO where do these come from
return `/brooklyn-ui-catalog/#!/bundles/${$scope.config.catalogBundleId}/${urlPartVersion}/types/${$scope.config.catalogBundleSymbolicName}/${urlPartVersion}`;
}
};
$scope.title = $scope.getTitle();
$scope.catalogURL = $scope.getCatalogURL();
$scope.catalogBomPrefix = 'catalog-bom-';
$scope.$watch('state.view', (newValue, oldValue) => {
if (newValue !== oldValue) {
$scope.title = $scope.getTitle();
$scope.catalogURL = $scope.getCatalogURL();
}
});
/* END Derived properties */
const allTypes = [];
const allBundles = [];
// Prepare resources for analysis if this is not an Update request.
if (!$scope.isUpdate()) {
// Get all types and bundles for analysis.
const promiseTypes = paletteApi.getTypes({params: {versions: 'all'}}).then(data => {
allTypes.push(...data);
}).catch(error => {
$scope.state.error = error;
});
const promiseBundles = paletteApi.getBundles({params: {versions: 'all', detail: true}}).then(data => {
allBundles.push(...data);
}).catch(error => {
$scope.state.error = error;
});
function checkIfBundleExists() {
const bundleName = getBundleId();
if (allBundles.find(item => item.symbolicName === bundleName)) {
$scope.showAdvanced = true;
$scope.state.warning = `Bundle with name "${bundleName}" exists already.`;
} else {
$scope.state.warning = undefined;
}
}
Promise.all([promiseTypes, promiseBundles]).then(() => {
console.info(`Loaded ${allBundles.length} bundles and ${allTypes.length} types for analysis.`)
// Trigger an initial bundle name check.
checkIfBundleExists();
});
// Watch for bundle name and display warning if bundle exists already.
$scope.$watchGroup(['config.current.bundle', 'config.local.default.bundle'], () => {
checkIfBundleExists();
});
}
$scope.save = () => {
$scope.state.saving = true;
$scope.state.error = undefined;
// Analyse existing catalog bundles if this is not an Update request.
if (!$scope.isUpdate()) {
const thisBundle = getBundleId();
const bundles = [];
const uniqueBundlesIds = new Set();
// Check if type exists in other bundles.
bundles.push(...allTypes.filter(item => item.symbolicName === getSymbolicName()).map(item => item.containingBundle));
bundles.forEach(item => uniqueBundlesIds.add(item.split(':')[0]));
if (uniqueBundlesIds.size > 0 && !uniqueBundlesIds.has(thisBundle)) {
$scope.state.error = `This type cannot be saved in bundle "${thisBundle}" from the composer because ` +
`it would conflict with a type with the same ID "${getSymbolicName()}" in ${bundles.map(item => `"${item}"`).join(', ')}.`;
$scope.showAdvanced = true;
$scope.state.saving = false;
return; // DO NOT SAVE!
}
// Check if any of existing bundles include other types.
if (uniqueBundlesIds.size) {
const bundlesWithMultipleTypes = bundles.filter(bundle => {
const [bundleName, bundleVersion] = bundle.split(':');
if (bundleName !== thisBundle) {
return false;
}
const existingBundle = allBundles.find(item => item.symbolicName === bundleName && item.version === bundleVersion);
const otherTypes = existingBundle.types.filter(item => item.symbolicName !== getSymbolicName())
return otherTypes.length > 0;
});
if (!$scope.state.error && bundlesWithMultipleTypes.length) {
$scope.state.error = `This type cannot be saved in bundle "${thisBundle}" from the composer because ` +
`${bundlesWithMultipleTypes.map(item => `"${item}"`).join(', ')} include${bundlesWithMultipleTypes.length > 1 ? '' : 's'} other types.`;
$scope.showAdvanced = true;
$scope.state.saving = false;
return; // DO NOT SAVE!
}
}
}
// Now, try to save.
let bom = createBom();
$scope.config.initial = $scope.config.current;
paletteApi.create(bom, {forceUpdate: $scope.state.force})
.then((savedItem) => {
if (!angular.isArray($scope.config.versions)) {
$scope.config.versions = [];
}
$scope.config.versions.push($scope.config.current.version);
$scope.state.view = VIEWS.saved;
})
.catch(error => {
$scope.state.error = error.error.message;
})
.finally(() => {
$scope.state.saving = false;
});
};
function getBundleBase() {
return $scope.config.current.bundle || $scope.config.local.default.bundle;
}
function getBundleId() {
return getBundleBase() && $scope.catalogBomPrefix + getBundleBase();
}
function getSymbolicName() {
return $scope.config.current.symbolicName || $scope.config.local.default.symbolicName;
}
function createBom() {
let blueprint = blueprintService.getAsJson();
const bundleBase = getBundleBase();
const bundleSymbolicName = getSymbolicName();
if (!bundleBase || !bundleSymbolicName) {
throw "Either the display name must be set, or the bundle and symbolic name must be explicitly set";
}
let bomItem = {
id: bundleSymbolicName,
itemType: $scope.config.current.itemType,
item: blueprint
};
// tags can now be added to a blueprint created in the YAML Editor
let tags = [];
if (blueprint.tags) {
tags = tags.concat(blueprint.tags);
delete blueprint['tags'];
}
if (blueprint['brooklyn.tags']) {
tags = [].concat(blueprint['brooklyn.tags']).concat(tags);
}
blueprint['brooklyn.tags'] = tags;
const bundleId = getBundleId();
let bomCatalogYaml = {
bundle: bundleId,
version: $scope.config.current.version,
items: [ bomItem ]
};
if(tags) {
bomCatalogYaml.tags = tags
}
let bundleName = $scope.config.current.name || $scope.config.local.default.name;
if (brUtilsGeneral.isNonEmpty(bundleName)) {
bomItem.name = bundleName;
}
if (brUtilsGeneral.isNonEmpty($scope.config.current.description)) {
bomItem.description = $scope.config.current.description;
}
if (brUtilsGeneral.isNonEmpty($scope.config.current.iconUrl)) {
bomItem.iconUrl = $scope.config.current.iconUrl;
}
$scope.config.catalogBundleId = bundleId;
$scope.config.catalogBundleSymbolicName = bundleSymbolicName;
return jsYaml.dump({ 'brooklyn.catalog': bomCatalogYaml });
}
let bundlize = $filter('bundlize');
$scope.updateDefaults = (newName) => {
if (!newName) newName = $scope.config.local.default.name;
$scope.config.local.default.symbolicName = $scope.config.default.symbolicName || ($scope.config.current.itemType==='template' && $scope.config.original.symbolicName) || bundlize(newName) || null;
$scope.config.local.default.bundle = $scope.config.default.bundle || ($scope.config.current.itemType==='template' && $scope.config.original.bundle) || bundlize(newName) || null;
};
$scope.$watchGroup(['config.current.name', 'config.current.itemType', 'config.current.bundle', 'config.current.symbolicName'], (newVals) => {
$scope.updateDefaults(newVals[0]);
$scope.form.name.$validate();
$scope.buttonText = $scope.buttonTextFn();
});
}