@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
855 lines • 128 kB
JavaScript
import { EventEmitter, Injectable } from '@angular/core';
import { ApplicationAvailability, ApplicationService, ApplicationType, InventoryService, TenantService } from '@c8y/client';
import { AlertService, AppStateService, gettext, HumanizeAppNamePipe, ModalService, PackageType, PluginsService, Status, WizardModalService, ZipService } from '@c8y/ngx-components';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { cloneDeep, get, groupBy, kebabCase, pick, uniqBy, omit, isUndefined } from 'lodash-es';
import { BehaviorSubject, defer } from 'rxjs';
import { debounceTime, take, map, filter, shareReplay } from 'rxjs/operators';
import { gt, coerce, satisfies } from 'semver';
import { EcosystemError } from './ecosystem-error';
import { APP_STATE, ERROR_MESSAGES } from './ecosystem.constants';
import { EcosystemWizards, ERROR_TYPE } from './ecosystem.model';
import * as i0 from "@angular/core";
import * as i1 from "@c8y/ngx-components";
import * as i2 from "@ngx-translate/core";
import * as i3 from "@c8y/client";
const CUMULOCITY_JSON = 'cumulocity.json';
const MICROSERVICE_NAME_MAX_LENGTH = 23;
export class EcosystemService {
constructor(modal, alertService, humanizeAppName, translateService, applicationService, appStateService, zipService, tenantService, inventoryService, wizardModalService, pluginService) {
this.modal = modal;
this.alertService = alertService;
this.humanizeAppName = humanizeAppName;
this.translateService = translateService;
this.applicationService = applicationService;
this.appStateService = appStateService;
this.zipService = zipService;
this.tenantService = tenantService;
this.inventoryService = inventoryService;
this.wizardModalService = wizardModalService;
this.pluginService = pluginService;
this.appDeleted = new EventEmitter();
this.progress = new BehaviorSubject(null);
this.appsGroupedByContextPath$ = defer(() => this.getWebApplications()).pipe(map(webApps => groupBy(webApps, 'contextPath')), shareReplay({ bufferSize: 1, refCount: true }));
}
getUniqueAppConfig(srcApp, existingApps) {
let app = {
name: srcApp.name,
key: srcApp.key,
contextPath: srcApp.contextPath
};
for (let retryNo = 0; retryNo < 9;) {
if (this.checkIfAppNameKeyPathExists(existingApps, app, retryNo)) {
retryNo++;
app = {
name: [srcApp.name, retryNo].join('-'),
key: [srcApp.key, retryNo].join('-'),
contextPath: [srcApp.contextPath, retryNo].join('-')
};
}
else {
return app;
}
}
return app;
}
/**
* Verify versions compatibility for blueprints. If a blueprint version
* is not compatible, a warning is shown.
*
* @param blueprint The blueprint to install.
* @returns true if the installation can continue or false if it should be aborted.
*/
async verifyBlueprintVersionsCompatibility(blueprint) {
const api = await this.getPlatformVersion();
if (blueprint.versioningMatrix) {
try {
const pluginApiVersion = blueprint.versioningMatrix[blueprint.version].api;
if (!satisfies(api, pluginApiVersion)) {
return await this.showModal(blueprint, false, api);
}
}
catch {
return await this.showModal(blueprint, false, api);
}
}
return true;
}
/**
* Verify versions compatibility for plugins. In case a version does not exist in the
* versioningMatrix we don't do anything due to backward compatibility. If a plugin version
* is not compatible, a warning is shown.
*
* @param pluginsToInstall The list of plugins to install.
* @returns true if the installation can continue or false if it should be aborted.
*/
async verifyPluginVersionsCompatibility(pluginsToInstall, app) {
const api = await this.getPlatformVersion();
const sdk = app.manifest?.webSdkVersion;
for (const plugin of pluginsToInstall) {
if (plugin.versioningMatrix) {
try {
const pluginSdkVersion = plugin.versioningMatrix[plugin.version].sdk;
const pluginApiVersion = plugin.versioningMatrix[plugin.version].api;
if (!satisfies(sdk, pluginSdkVersion) || !satisfies(api, pluginApiVersion)) {
return await this.showModal(plugin, true, api, sdk);
}
}
catch {
return await this.showModal(plugin, false, api, sdk);
}
}
}
return true;
}
/**
* Community plugins need to verify the license agreement. If a package is a community
* package, the license is shown.
*
* @param pluginsToInstall The list of plugins to install.
* @returns true if the installation can continue.
*/
async verifyLicenses(pluginsToInstall) {
let _resolve;
const result = new Promise(resolve => {
_resolve = resolve;
});
pluginsToInstall = pluginsToInstall.filter(plugin => plugin.type !== PackageType.CUSTOM && plugin.type !== PackageType.OFFICIAL);
if (pluginsToInstall.length === 0) {
return Promise.resolve(true);
}
const initialState = {
id: EcosystemWizards.LICENSE_CONFIRM,
componentInitialState: {
pluginsToInstall
}
};
const modalOptions = { initialState };
const wizard = this.wizardModalService.show(modalOptions);
wizard.content.onClose.subscribe(confirmed => {
_resolve(confirmed);
});
return result;
}
/**
* If the plugin is archived, a warning should be shown.
*
* @param pluginsToInstall The list of plugins to install.
* @returns true if the installation can continue.
*/
async verifyArchived(pluginsToInstall) {
let _resolve;
const result = new Promise(resolve => {
_resolve = resolve;
});
pluginsToInstall = pluginsToInstall.filter(plugin => plugin.type === PackageType.ARCHIVED);
if (pluginsToInstall.length === 0) {
return Promise.resolve(true);
}
const initialState = {
id: EcosystemWizards.ARCHIVED_CONFIRM
};
const modalOptions = { initialState };
const wizard = this.wizardModalService.show(modalOptions);
wizard.content.onClose.subscribe(confirmed => {
_resolve(confirmed);
});
return result;
}
/**
* @description
* Compares currently deployed application version with application version tagged as "latest"
*
* @param {string} currentApplicationVersion Deployed application version
* @param {object} latestApp Latest application version object
*
* @returns {boolean} Returns true if latest version is greater than current, otherwise false
*/
shouldUpgradePackage(currentApplicationVersion, latestApp) {
const latestApplicationVersion = latestApp?.version;
if (!latestApplicationVersion || !currentApplicationVersion) {
return false;
}
return gt(coerce(latestApplicationVersion), coerce(currentApplicationVersion));
}
/**
* @description
* Gets an object that contains searched tag
*
* @param {array} applicationVersions Array with all available versions
* @param {string} tagName Searched tag
*
* @returns {object} Returns an object with searched tag
*/
getApplicationVersionObjectByTag(applicationVersions, tagName) {
return applicationVersions?.find(element => element.tags.includes(tagName));
}
async getApplication(appId) {
return (await this.applicationService.detail(appId)).data;
}
getApplications(customFilter = {}) {
const filter = {
pageSize: 2000,
withTotalPages: true
};
Object.assign(filter, customFilter);
const currentTenant = this.appStateService.currentTenant.value;
return this.applicationService.listByTenant(currentTenant.name, filter);
}
async getMicroservices() {
const apps = (await this.getApplications()).data;
const microservices = apps.filter(app => this.isMicroservice(app));
return microservices.sort((a, b) => a.name.localeCompare(b.name));
}
async getWebApplications(customFilter = {}) {
const apps = (await this.getApplications(customFilter)).data;
const webApps = apps.filter(app => this.isApplication(app));
return webApps.sort((a, b) => a.name.localeCompare(b.name));
}
async getFeatureApplications(customFilter = {}) {
const apps = (await this.getApplications(customFilter)).data;
const webApps = apps.filter(app => this.isFeature(app));
return webApps.sort((a, b) => a.name.localeCompare(b.name));
}
async getPackageApplications(customFilter = {}) {
const filterCallback = app => this.isPackage(app);
return this.getApplicationsFiltered(customFilter, filterCallback);
}
async getHostedAndPackageApplications(customFilter = {}) {
const filterCallback = app => this.isPackage(app) || this.isApplication(app);
return this.getApplicationsFiltered(customFilter, filterCallback);
}
async getApplicationsFiltered(customFilter = {}, filterCallback = (app) => !!app) {
const filter = Object.assign({}, customFilter);
const sharedFilter = Object.assign({
availability: ApplicationAvailability.SHARED,
type: 'HOSTED',
pageSize: 2000
}, customFilter);
const [{ data: apps }, { data: shared }] = await Promise.all([
this.getApplications(filter),
this.applicationService.list(sharedFilter)
]);
const webApps = [...apps, ...shared].filter(filterCallback);
// an app could be subscribed to a tenant, but also have it's availability set to SHARED, in that case it would occur twice.
const uniqWebApps = uniqBy(webApps, (app) => app.id);
return uniqWebApps.sort((a, b) => a.name.localeCompare(b.name));
}
async isMicroserviceHostingAllowed() {
const { data: apps } = await this.applicationService.listByName('feature-microservice-hosting');
return !!apps.filter(app => app.owner?.tenant?.id === 'management').length;
}
canOpenAppInBrowser(app) {
const isNotAFeature = !this.isFeature(app);
const hasProperType = [ApplicationType.HOSTED, ApplicationType.EXTERNAL].includes(app.type);
const isNotPackage = !this.isPackage(app);
return isNotAFeature && hasProperType && isNotPackage;
}
openApp(app) {
window.open(this.applicationService.getHref(app), '_blank', 'noopener,noreferrer');
}
async canDeleteApp(app) {
return (this.isOwner(app) && (!this.isCurrentApp(app) || (await this.hasSubscribedAppParent(app))));
}
isOwner(app) {
const currentTenant = this.appStateService.currentTenant.value;
const appOwner = get(app, 'owner.tenant.id');
return currentTenant?.name === appOwner;
}
isFeature(app) {
return !!app.name.match(/feature-/);
}
isMicroservice(app) {
return app.type === 'MICROSERVICE';
}
isExternal(app) {
return app.type === 'EXTERNAL';
}
isPackage(app) {
return app.manifest?.isPackage === true;
}
isPlugin(app) {
return app.manifest?.package === 'plugin';
}
cancelAppCreation(app) {
if (this.xhr) {
this.xhr.abort();
}
if (app) {
this.applicationService.delete(app);
}
}
updateUploadProgress(event) {
if (event.lengthComputable) {
const currentProgress = this.progress.value;
this.progress.next(currentProgress + (event.loaded / event.total) * (95 - currentProgress));
}
}
setAppActiveVersion(app, activeVersionId) {
return this.applicationService.update({ id: app.id, activeVersionId });
}
setPackageVersionTag(app, version, tags) {
return this.applicationService.setPackageVersionTag(app, version, tags);
}
deletePackageVersion(app, params) {
return this.applicationService.deleteVersionPackage(app, params);
}
getHumanizedAppName(app) {
return this.humanizeAppName.transform(app.name).pipe(debounceTime(250), take(1)).toPromise();
}
createConfig(app, formGroupValue) {
const { id, type, availability } = app;
let config = pick(formGroupValue, ['name', 'key', 'contextPath']);
config = {
...config,
isSetup: true,
id,
type,
availability
};
return config;
}
async listArchives(appId) {
const filter = {
pageSize: 100
};
return (await this.applicationService.binary(appId).list(filter)).data;
}
async deleteArchive(archive, app) {
const humanizedArchiveName = await this.getHumanizedAppName(archive);
try {
await this.modal.confirm(gettext('Delete archive'), this.translateService.instant(gettext(`You are about to delete archive "{{ humanizedArchiveName }}". Do you want to proceed?`), { humanizedArchiveName }), Status.DANGER, { ok: gettext('Delete'), cancel: gettext('Cancel') });
await this.applicationService.binary(app).delete(archive.id);
this.alertService.success(gettext('Archive deleted.'));
}
catch (ex) {
if (ex) {
this.alertService.danger(get(ex, 'data.message'), ex.data);
}
throw new Error('Cancelled');
}
}
async getArchiveManagedObject(binaryId) {
return (await this.inventoryService.detail(binaryId)).data;
}
async downloadArchive(app, archive) {
try {
const binary = await this.getBinary(app, archive);
const fileBinary = new Blob([binary], { type: 'application/x-zip-compressed' });
saveAs(fileBinary, archive.name);
}
catch (e) {
// empty
}
}
async updateApp(app, deleteOnFailure = false) {
try {
return await this.applicationService.update(app);
}
catch (ex) {
this.alertError(ex);
if (deleteOnFailure) {
await this.applicationService.delete(app.id);
throw new EcosystemError(ERROR_TYPE.APPLICATION_CREATION_FAILED);
}
}
}
async deleteApp(app, silent = false) {
const humanizedAppName = await this.getHumanizedAppName(app);
if (!silent) {
await this.modal.confirm(gettext('Delete application'), this.translateService.instant(gettext(`You are about to delete application "{{ humanizedAppName }}". Do you want to proceed?`), { humanizedAppName }), Status.DANGER, { ok: gettext('Delete'), cancel: gettext('Cancel') });
}
await this.applicationService.delete(app.id);
if (!silent) {
this.alertService.success(gettext('Application deleted.'));
}
this.appDeleted.emit(app);
}
async checkIfSubscribed(app) {
const currentTenant = await this.tenantService.current();
const subscribedApps = currentTenant.data.applications.references;
return subscribedApps.some(application => application.application.id === app.id);
}
async subscribeApp(app) {
const currentTenant = this.appStateService.currentTenant.value;
try {
await this.tenantService.subscribeApplication(currentTenant, app);
this.alertService.success(gettext('Successfully subscribed to application.'));
}
catch (ex) {
this.alertError(ex);
}
}
async unsubscribeApp(app) {
const currentTenant = this.appStateService.currentTenant.value;
try {
await this.tenantService.unsubscribeApplication(currentTenant, app);
this.alertService.success(gettext('Successfully unsubscribed from application.'));
}
catch (ex) {
this.alertError(ex);
}
}
async isValidAppType(archive, appType) {
try {
const currentType = await this.getAppType(archive);
if (currentType !== appType) {
throw new EcosystemError(ERROR_TYPE.TYPE_VALIDATION);
}
else {
this.progress.next(this.progress.value + 10);
return true;
}
}
catch (ex) {
throw new EcosystemError(ERROR_TYPE.TYPE_VALIDATION);
}
}
async uploadArchiveToApp(archive, app, isNewVersion = false) {
let uploadOverrides;
if (isNewVersion) {
uploadOverrides = await this.getUploadOverrides(archive, app);
}
const binaryService = this.applicationService.binary(app);
this.xhr = binaryService.uploadWithProgressXhr(archive, this.updateUploadProgress.bind(this), '', uploadOverrides);
const binaryMo = await binaryService.getXMLHttpResponse(this.xhr);
// TODO commented it due to: https://cumulocity.atlassian.net/browse/MTM-48553
// Add it back when BE fixes issues with activeVersion.
// if (isNewVersion) {
// return await this.getApplication(app);
// }
const notInitialPackage = app.applicationVersions?.length > 0;
if (notInitialPackage) {
return (await this.applicationService.update({ id: app.id })).data;
}
return (await this.setAppActiveVersion(app, (binaryMo.binaryId || binaryMo.id))).data;
}
async validateArchiveToAppCompatibility(archive, app) {
const appType = await this.getAppType(archive);
if (appType !== app.type) {
throw new EcosystemError(ERROR_TYPE.INVALID_APPLICATION);
}
else {
this.progress.next(this.progress.value + 10);
}
if (this.isMicroservice(app)) {
return;
}
const manifest = await this.getCumulocityJson(archive);
// A user can upload an app without a Cumulocity JSON file (e.g. a react app).
// This is allowed and should not trigger a validation error.
if (!manifest) {
return;
}
await this.validatePackageKeyAndContextPath(manifest, app);
}
async getCumulocityJson(archive) {
try {
const c8yManifest = await this.getCumulocityJson$(archive).toPromise();
return c8yManifest;
}
catch (ex) {
return null;
}
}
async createAppForArchive(archive, isPackageTypeArchive = false) {
let isPackage = false;
const appType = await this.getAppType(archive);
let appModel = {};
const supportedAppTypes = [ApplicationType.HOSTED, ApplicationType.MICROSERVICE];
if (supportedAppTypes.includes(appType)) {
try {
appModel = await this.getCumulocityJson$(archive).toPromise();
isPackage = appModel.isPackage;
}
catch (e) {
// do nothing, we allow having HOSTED applications without the manifest file
}
}
const name = this.getBaseNameFromArchiveOrAppModel(archive, appType, appModel);
const clearedName = this.removeForbiddenCharacters(name);
const key = this.getAppKey(appModel, clearedName);
const contextPath = this.getContextPath(appModel, name);
const appToSave = {
type: appType,
name,
key,
contextPath
};
if (isPackageTypeArchive && !isPackage) {
throw new EcosystemError(ERROR_TYPE.INVALID_PACKAGE);
}
else if (!isPackageTypeArchive && isPackage) {
throw new EcosystemError(ERROR_TYPE.INVALID_APPLICATION);
}
else if (this.isNameLengthExceeded(name, appType)) {
const error = new Error();
error.name = ERROR_TYPE.MICROSERVICE_NAME_TOO_LONG;
error.message = this.translateService.instant(ERROR_MESSAGES[error.name], {
name,
maxChars: MICROSERVICE_NAME_MAX_LENGTH
});
throw error;
}
return (await this.applicationService.create({
...appToSave,
manifest: {
isPackage,
...(appModel?.package && { package: appModel.package })
}
})).data;
}
async reactivateArchive(app) {
try {
await this.applicationService.reactivateArchive(app.id);
this.alertService.success(gettext('Application reactivated.'));
}
catch (ex) {
this.alertError(ex);
}
}
async removeOldestArchive(app, archives) {
try {
await this.modal.confirm(gettext('Delete oldest archive and continue'), gettext('Up to 6 archives can be saved in the platform. If you upload a new archive, the oldest archive that is not active will be deleted. Do you want to proceed?'), Status.INFO, { ok: gettext('Delete and continue') });
const archiveToDelete = archives[archives.length - 2];
await this.applicationService.binary(app).delete(archiveToDelete.id);
this.alertService.success(gettext('Archive deleted.'));
}
catch (ex) {
if (ex) {
this.alertError(ex);
}
else {
return Promise.reject('cancelled');
}
}
}
async deployApp(selectedPackage, formGroupValue, model) {
// Create new app config
const config = this.createConfig(selectedPackage, formGroupValue);
const requestedVersion = model.selected.version;
let cleanManifest;
try {
const manifest = await this.applicationService.getAppManifest(selectedPackage, requestedVersion);
cleanManifest = omit(manifest, ['name', 'contextPath', 'key']);
}
catch (ex) {
throw new EcosystemError(ERROR_TYPE.VERSION_NOT_FOUND);
}
config.isSetup = true;
config.manifest = cleanManifest;
config.availability = ApplicationAvailability.PRIVATE;
config.manifest.isPackage = false;
config.manifest.source = selectedPackage.id;
config.manifest.package = 'blueprint';
// because of a issue with SHARED availability we always should check again
// if the app not exist already
const allExistingApps = await this.getHostedAndPackageApplications();
const doesAppKeyOrContextPathExist = this.checkIfAppNameKeyPathExists(allExistingApps, config);
if (doesAppKeyOrContextPathExist) {
throw new EcosystemError(ERROR_TYPE.ALREADY_EXIST);
}
// Create new app
const newApp = (await this.applicationService.create(config)).data;
try {
// Binary upload can fail if SHARED package
// in this case, catch error and fall back to
// clone API.
await this.uploadBinaryFromOtherPackage(selectedPackage, newApp, model.selected.binaryId, requestedVersion);
}
catch (error) {
if (error?.res?.status === 404) {
await this.fallbackToClone(newApp, selectedPackage, requestedVersion);
}
}
// update the icon if a custom one is selected
if (formGroupValue.icon) {
await this.applicationService.update({
id: newApp.id,
config: { icon: formGroupValue.icon }
});
}
return newApp;
}
async fallbackToClone(application, selectedPackage, requestedVersion) {
let wasSuccess = true;
let clonedPkg;
try {
clonedPkg = (await this.applicationService.clone(selectedPackage, requestedVersion)).data;
await this.uploadBinaryFromOtherPackage(selectedPackage, application, clonedPkg.activeVersionId, requestedVersion, clonedPkg);
}
catch (error) {
this.alertError(error);
wasSuccess = false;
}
finally {
await this.deleteApp(clonedPkg, true);
}
return wasSuccess;
}
async uploadBinaryFromOtherPackage(selectedPackage, applicationToUploadBinaryTo, binaryId, requestedVersion, useBinariesFrom) {
const { data: binaryDetails } = await this.inventoryService.detail(binaryId);
// Get binary from specific package version
const binary = await this.getBinary(useBinariesFrom || selectedPackage, {
id: binaryId
});
// Create zip
const fileBinary = new Blob([binary], { type: binaryDetails.contentType });
const file = new File([fileBinary], binaryDetails.name, {
type: binaryDetails.contentType
});
// Upload binary to new app
await this.uploadArchiveToApp(file, applicationToUploadBinaryTo);
// update the app manifest
await this.updateAppManifest(applicationToUploadBinaryTo, selectedPackage, requestedVersion);
}
getAppState(app) {
if (!this.isOwner(app)) {
return APP_STATE.SUBSCRIBED;
}
else if (this.isUnpacked(app)) {
return APP_STATE.UNPACKED;
}
else if (app.type === ApplicationType.EXTERNAL) {
return APP_STATE.EXTERNAL;
}
return APP_STATE.CUSTOM;
}
getPackageContentState(app) {
if (!this.isPackage(app)) {
return;
}
if (this.isPackageBlueprint(app)) {
return APP_STATE.PACKAGE_BLUEPRINT;
}
if (this.isPluginsPackage(app)) {
return APP_STATE.PACKAGE_PLUGIN;
}
return APP_STATE.PACKAGE_UNKNOWN;
}
isPackageBlueprint(app) {
return this.isPackage(app) && app.manifest.package === 'blueprint';
}
isPluginsPackage(app) {
return this.isPackage(app) && app.manifest.package === 'plugin';
}
isUnpacked(app) {
return !!app.manifest?.source;
}
hasExports(app) {
return !!app.manifest?.exports?.length;
}
isApplication(app) {
return (app.type !== ApplicationType.MICROSERVICE && !this.isFeature(app) && !this.isPackage(app));
}
isCustomMicroservice(app) {
return this.isOwner(app) && app.type === ApplicationType.MICROSERVICE;
}
async getBinary(app, archive) {
let binary;
try {
const res = await this.applicationService.binary(app).downloadArchive(archive.id);
binary = await res.arrayBuffer();
}
catch (ex) {
const msg = gettext('Could not get the binary.');
this.alertService.danger(msg);
}
return binary;
}
async isOverwrittenByCustomApp(app) {
return !this.isOwner(app) && (await this.hasSubscribedAppParent(app));
}
async hasSubscribedAppParent(app) {
const appsGroupedByContextPath = await this.appsGroupedByContextPath$.pipe(take(1)).toPromise();
return app.contextPath && appsGroupedByContextPath[app.contextPath]?.length === 2;
}
/**
* @deprecated
*/
setAvailabilityToPrivateIfNotSetAlready(app) {
app.availability = ApplicationAvailability.PRIVATE;
return app;
}
/**
* Shows an error dialog.
* @param error Either a server error or an internal [[EcosystemError]].
*/
alertError(error) {
if (error instanceof EcosystemError) {
this.alertService.danger(error.message);
}
else {
this.alertService.addServerFailure(error);
}
}
async validatePackageKeyAndContextPath(manifest, app) {
const contextPath = get(manifest, 'contextPath');
const appKey = get(manifest, 'key');
if (contextPath !== app.contextPath || appKey !== app.key) {
throw new EcosystemError(ERROR_TYPE.KEY_OR_CONTEXT_PATH_MISMATCH);
}
}
filterContainString(name, filterTerm) {
const term = filterTerm.toLowerCase().trim();
return name && name.toLowerCase().indexOf(term) > -1;
}
async updateAppManifest(application, selectedPackage, requestedVersion) {
const { id } = application;
const cleanedApp = this.removeAppProperties(application);
let manifest = selectedPackage.manifest || {};
if (requestedVersion) {
try {
const versionedAppManifest = await this.applicationService.getAppManifest(selectedPackage, requestedVersion);
manifest = versionedAppManifest;
}
catch (ex) {
throw new EcosystemError(ERROR_TYPE.VERSION_NOT_FOUND);
}
}
cleanedApp.manifest = manifest;
cleanedApp.manifest.isPackage = false;
cleanedApp.manifest.source = selectedPackage.id;
return await this.applicationService
.binary(id)
.updateFiles([{ path: CUMULOCITY_JSON, contents: JSON.stringify(cleanedApp) }]);
}
/**
* Creates object containing properties for filtering applications on lists.
*
* @param {object} app Application to create filter properties from.
* @returns {object} Properties to filter by applications list.
*/
getAppFilterProps(app) {
return {
type: this.pluginService.getPackageType(app),
availability: this.getAppState(app)?.label,
content: this.getPackageContentState(app)?.label
};
}
checkIfAppNameKeyPathExists(existingApps, app, retryNo) {
if (isUndefined(retryNo)) {
return existingApps.find(existingApp => existingApp.name === app.name ||
existingApp.key === app.key ||
existingApp.contextPath === app.contextPath);
}
return existingApps.find(existingApp => existingApp.name === app.name ||
existingApp.key === app.key ||
existingApp.contextPath === app.contextPath ||
existingApp.name === [app.name, retryNo].join('-') ||
existingApp.key === [app.key, retryNo].join('-') ||
existingApp.contextPath === [app.contextPath, retryNo].join('-'));
}
async showModal(packageType, isPlugin, apiVersion, sdkVersion) {
try {
const messages = {
plugin: {
title: gettext('Plugin installation'),
body: sdkVersion
? gettext('The current version of the plugin that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}" or SDK version "{{ sdkVersion }}". Do you want to proceed?')
: gettext('The current version of the plugin that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}". Do you want to proceed?')
},
blueprint: {
title: gettext('Blueprint installation'),
body: sdkVersion
? gettext('The current version of the blueprint that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}" or SDK version "{{ sdkVersion }}". Do you want to proceed?')
: gettext('The current version of the blueprint that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}". Do you want to proceed?')
}
};
const entityType = isPlugin ? 'plugin' : 'blueprint';
const selectedMessages = messages[entityType];
const translatedBody = this.translateService.instant(selectedMessages.body, {
name: packageType.name,
apiVersion,
sdkVersion
});
await this.modal.confirm(selectedMessages.title, translatedBody, 'warning', {
ok: gettext('Continue'),
cancel: gettext('Cancel')
});
}
catch {
// modal canceled
return false;
}
return true;
}
async getPlatformVersion() {
return await this.appStateService.state$
.pipe(take(1), map(state => state?.versions?.backend), filter(backendVersion => !!backendVersion))
.toPromise();
}
getAppKey(appModel, name) {
let key = appModel?.key;
if (!key) {
key = `${kebabCase(name)}-key`;
}
return key;
}
getContextPath(appModel, name) {
return appModel?.contextPath || name.toLowerCase();
}
removeForbiddenCharacters(str) {
return str.replace(/[^a-zA-Z0-9-_]/g, '');
}
isCurrentApp(app) {
const currentApp = this.appStateService.state.app;
return currentApp.contextPath === app.contextPath;
}
getCumulocityJson$(archive) {
return this.zipService.getJsonData(archive, {
filename: CUMULOCITY_JSON
});
}
getAppType(archive) {
return this.getCumulocityJson$(archive)
.toPromise()
.then(data => get(data, 'type') ||
(get(data, 'apiVersion') ? ApplicationType.MICROSERVICE : ApplicationType.HOSTED))
.catch(() => ApplicationType.HOSTED);
}
getBaseNameFromArchiveOrAppModel(archive, appType, appModel) {
let baseName = appModel?.name || archive.name.replace(/\.zip$/i, '');
if (appType === 'MICROSERVICE') {
baseName = this.removeVersionFromName(baseName);
}
return baseName;
}
removeAppProperties(app) {
const tempApp = cloneDeep(app);
const propertiesToRemove = ['id', 'owner', 'activeVersionId', 'self', 'type'];
propertiesToRemove.forEach(prop => delete tempApp[prop]);
return tempApp;
}
async getUploadOverrides(archive, app) {
const { version } = await this.getCumulocityJson$(archive).toPromise();
const isInitialPackage = app.applicationVersions?.length === 0;
return {
listUrl: 'versions',
headers: {
Accept: 'application/vnd.com.nsn.cumulocity.applicationVersion+json;charset=UTF-8;ver=0.9'
},
bodyFileProperty: 'applicationBinary',
requestBody: {
applicationVersion: { version, ...(isInitialPackage && { tags: ['latest'] }) }
}
};
}
removeVersionFromName(name) {
const versionRegExp = /-\d+\.\d+\.\d+(\.\d+)?(-\d+)?(.*)$/;
return name.replace(versionRegExp, '');
}
isNameLengthExceeded(name, appType) {
return name.length > MICROSERVICE_NAME_MAX_LENGTH && appType === ApplicationType.MICROSERVICE;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EcosystemService, deps: [{ token: i1.ModalService }, { token: i1.AlertService }, { token: i1.HumanizeAppNamePipe }, { token: i2.TranslateService }, { token: i3.ApplicationService }, { token: i1.AppStateService }, { token: i1.ZipService }, { token: i3.TenantService }, { token: i3.InventoryService }, { token: i1.WizardModalService }, { token: i1.PluginsService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EcosystemService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EcosystemService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i1.ModalService }, { type: i1.AlertService }, { type: i1.HumanizeAppNamePipe }, { type: i2.TranslateService }, { type: i3.ApplicationService }, { type: i1.AppStateService }, { type: i1.ZipService }, { type: i3.TenantService }, { type: i3.InventoryService }, { type: i1.WizardModalService }, { type: i1.PluginsService }] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWNvc3lzdGVtLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9lY29zeXN0ZW0vc2hhcmVkL2Vjb3N5c3RlbS5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxZQUFZLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXpELE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIsa0JBQWtCLEVBQ2xCLGVBQWUsRUFXZixnQkFBZ0IsRUFJaEIsYUFBYSxFQUNkLE1BQU0sYUFBYSxDQUFDO0FBQ3JCLE9BQU8sRUFDTCxZQUFZLEVBRVosZUFBZSxFQUNmLE9BQU8sRUFDUCxtQkFBbUIsRUFDbkIsWUFBWSxFQUNaLFdBQVcsRUFDWCxjQUFjLEVBQ2QsTUFBTSxFQUNOLGtCQUFrQixFQUNsQixVQUFVLEVBQ1gsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2hHLE9BQU8sRUFBRSxlQUFlLEVBQWMsS0FBSyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzFELE9BQU8sRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUUsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQy9DLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUNuRCxPQUFPLEVBQW9CLFNBQVMsRUFBRSxjQUFjLEVBQUUsTUFB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