UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1 lines • 222 kB
{"version":3,"file":"c8y-ngx-components-ecosystem-shared.mjs","sources":["../../ecosystem/shared/ecosystem.model.ts","../../ecosystem/shared/ecosystem.constants.ts","../../ecosystem/shared/ecosystem-error.ts","../../ecosystem/shared/ecosystem.service.ts","../../ecosystem/shared/package-availability.service.ts","../../ecosystem/shared/add-application.component.ts","../../ecosystem/shared/add-application.component.html","../../ecosystem/shared/translate-package-label.pipe.ts","../../ecosystem/shared/application-card.component.ts","../../ecosystem/shared/application-card.component.html","../../ecosystem/shared/application-properties-form.component.ts","../../ecosystem/shared/application-properties-form.component.html","../../ecosystem/shared/duplicate-application/duplicate-application-list/duplicate-application-list.component.ts","../../ecosystem/shared/duplicate-application/duplicate-application-list/duplicate-application-list.component.html","../../ecosystem/shared/duplicate-application/duplicate-application-properties/duplicate-application-properties.component.ts","../../ecosystem/shared/duplicate-application/duplicate-application-properties/duplicate-application-properties.component.html","../../ecosystem/shared/duplicate-application/duplicate-application.component.ts","../../ecosystem/shared/duplicate-application/duplicate-application.component.html","../../ecosystem/shared/package-version-select/package-version-select.component.ts","../../ecosystem/shared/package-version-select/package-version-select.component.html","../../ecosystem/shared/upload-archive.component.ts","../../ecosystem/shared/upload-archive.component.html","../../ecosystem/shared/list-filters/list-filters.component.ts","../../ecosystem/shared/list-filters/list-filters.component.html","../../ecosystem/shared/archived-filter/archived-filter.component.ts","../../ecosystem/shared/archived-filter/archived-filter.component.html","../../ecosystem/shared/package-changelog/package-changelog.component.ts","../../ecosystem/shared/package-changelog/package-changelog.component.html","../../ecosystem/shared/shared-ecosystem.module.ts","../../ecosystem/shared/list-filters/list-filters.model.ts","../../ecosystem/shared/c8y-ngx-components-ecosystem-shared.ts"],"sourcesContent":["import { ApplicationPlugin } from '@c8y/ngx-components';\n\n/** Wizard types */\nexport enum EcosystemWizards {\n APPLICATION_UPLOAD = 'ecosystemApplicationUpload',\n MICROSERVICE_UPLOAD = 'ecosystemMicroserviceUpload',\n PACKAGE_UPLOAD = 'ecosystemPackageUpload',\n BLUEPRINT_DEPLOYMENT = 'ecosystemBlueprintDeployment',\n LICENSE_CONFIRM = 'ecosystemLicenseConfirm',\n ARCHIVED_CONFIRM = 'ecosystemArchivedConfirm'\n}\n\nexport enum ERROR_TYPE {\n TYPE_VALIDATION = 'TYPE_VALIDATION',\n ALREADY_SUBSCRIBED = 'ALREADY_SUBSCRIBED',\n ALREADY_EXIST = 'ALREADY_EXIST',\n INTERNAL_ERROR = 'INTERNAL_ERROR',\n NO_MANIFEST_FILE = 'NO_MANIFEST_FILE',\n INVALID_PACKAGE = 'INVALID_PACKAGE',\n INVALID_APPLICATION = 'INVALID_APPLICATION',\n MICROSERVICE_NAME_TOO_LONG = 'MICROSERVICE_NAME_TOO_LONG',\n APPLICATION_CREATION_FAILED = 'APPLICATION_CREATION_FAILED',\n KEY_OR_CONTEXT_PATH_MISMATCH = 'KEY_OR_CONTEXT_PATH_MISMATCH',\n VERSION_NOT_FOUND = 'VERSION_NOT_FOUND'\n}\n\nexport type LicensedApplicationPlugin = Pick<\n ApplicationPlugin,\n 'type' | 'license' | 'name' | 'version' | 'contextPath'\n>;\n\nexport const PRODUCT_EXPERIENCE_ECOSYSTEM = {\n APPLICATIONS: {\n EVENTS: {\n AVAILABILITY: 'availability',\n APPLICATION_CARD: 'applicationCard',\n APPLICATION_PROPERTIES: 'applicationProperties',\n DEPLOY_APPLICATION: 'deployApplication',\n DUPLICATE_APPLICATION: 'duplicateApplication',\n INSTALLED_PLUGINS: 'installedPlugins',\n PACKAGE_PLUGINS: 'packagePlugins',\n PACKAGE_VERSIONS: 'packageVersions',\n FILTER_LIST: 'filterList'\n },\n COMPONENTS: {\n APPLICATION_CARD: 'application-card',\n APPLICATION_PLUGINS: 'application-plugins',\n APPLICATION_PROPERTIES: 'application-properties',\n DEPLOY_APPLICATION: 'deploy-application',\n DUPLICATE_APPLICATION_PROPERTIES: 'duplicate-application-properties',\n PLUGIN_LIST: 'plugin-list',\n PACKAGE_VERSIONS: 'package-versions-list',\n UPDATE_PLUGIN_OF_APP: 'update-plugin-of-app',\n LIST_FILTERS: 'list-filters',\n PACKAGE_DETAILS: 'package-details'\n },\n ACTIONS: {\n AVAILABILITY_CHANGE: 'availabilityChange',\n CANCEL: 'cancel',\n CLONE: 'clone',\n CHANGE_PLUGIN_VERSION: 'changePluginVersion',\n DELETE: 'delete',\n DOWNLOAD: 'download',\n DEPLOY_APPLICATION: 'deployApplication',\n EDIT: 'edit',\n INSTALL_PLUGIN: 'installPlugin',\n INSTALL_PLUGINS: 'installPlugins',\n UNINSTALL_PLUGIN: 'uninstallPlugin',\n SELECT_VERSION: 'selectVersion',\n SET_AS_LATEST: 'setAsLatest',\n UPDATE_AVAILABLE: 'updateAvailable',\n UPLOAD: 'upload',\n SET_FILTER_TERM: 'setFilterTerm',\n SET_PREDEFINED_FILTERS: 'setPredefinedFilters',\n RESET_FILTER: 'resetFilter',\n INSTALL_PLUGINS_INITIATED: 'installPluginsInitiated',\n DEPLOY_APPLICATION_INITIATED: 'deployApplicationInitiated'\n },\n RESULTS: {\n DEPLOYED: 'deployed',\n DUPLICATED: 'duplicated',\n PLUGIN_INSTALLED: 'pluginInstalled',\n PLUGIN_REMOVED: 'pluginRemoved',\n PLUGIN_VERSION_CHANGED: 'pluginVersionChanged',\n SERVER_FAILURE: 'serverFailure',\n SUCCESS: 'success'\n }\n }\n} as const;\n","import { ERROR_TYPE } from './ecosystem.model';\nimport { gettext, PackageType, PropertiesListItem } from '@c8y/ngx-components';\n\nexport const ERROR_MESSAGES = {\n [ERROR_TYPE.ALREADY_EXIST]: gettext(\n 'Could not deploy the application, as an application with the same name`KEEP_ORIGINAL`, context-path`KEEP_ORIGINAL` or key`KEEP_ORIGINAL` exists already.'\n ),\n [ERROR_TYPE.TYPE_VALIDATION]: gettext(\n 'Wrong file format. Expected a *.zip file with a valid manifest.'\n ),\n [ERROR_TYPE.ALREADY_SUBSCRIBED]: gettext(\n 'Could not subscribe to the microservice because another application with the same context path is already subscribed.'\n ),\n [ERROR_TYPE.NO_MANIFEST_FILE]: gettext('Could not find a manifest.'),\n [ERROR_TYPE.INVALID_PACKAGE]: gettext('You have not uploaded a valid package.'),\n [ERROR_TYPE.INVALID_APPLICATION]: gettext('You have not uploaded a valid application.'),\n [ERROR_TYPE.INTERNAL_ERROR]: gettext('An internal error occurred, try to upload again.'),\n [ERROR_TYPE.MICROSERVICE_NAME_TOO_LONG]: gettext(\n 'Microservice name \"{{ name }}\" must not be longer than {{ maxChars }} characters.'\n ),\n [ERROR_TYPE.APPLICATION_CREATION_FAILED]: gettext('Application creation failed.'),\n [ERROR_TYPE.KEY_OR_CONTEXT_PATH_MISMATCH]: gettext(\n 'The \"contextPath`KEEP_ORIGINAL`\" or \"key`KEEP_ORIGINAL`\" of the uploaded archive do not match with the existing application.'\n ),\n [ERROR_TYPE.VERSION_NOT_FOUND]: gettext('The selected version was not found on the server.')\n};\n\nexport const APP_STATE = {\n SUBSCRIBED: {\n label: gettext('Subscribed`application`'),\n class: 'label-primary',\n tooltip: gettext('Provided by parent tenant.')\n },\n CUSTOM: {\n label: gettext('Custom`application`'),\n class: 'label-info',\n tooltip: gettext('Manually uploaded to the platform.')\n },\n EXTERNAL: {\n label: gettext('External`application`'),\n class: 'label-warning',\n tooltip: gettext('Application hosted outside of the platform.')\n },\n UNPACKED: {\n label: gettext('Unpacked`application`'),\n class: 'label-success',\n tooltip: gettext('Deployed from a package available under \"Packages\".')\n },\n PACKAGE_BLUEPRINT: {\n label: gettext('Blueprint'),\n class: 'label-success',\n tooltip: gettext('Contains an application and may include plugins.')\n },\n PACKAGE_PLUGIN: {\n label: gettext('Plugins'),\n class: 'label-info',\n tooltip: gettext('Contains only plugins.')\n },\n PACKAGE_UNKNOWN: {\n label: gettext('Unknown`package-type`'),\n class: 'label-info',\n tooltip: gettext('Package contents could not be determined.')\n }\n};\n\nexport type ApplicationState = (typeof APP_STATE)[keyof typeof APP_STATE];\n\nexport const PACKAGE_TYPE_LABELS = {\n [PackageType.COMMUNITY]: {\n label: gettext('COMMUNITY`Package created by the developer community.`'),\n tooltip: gettext('Package created by the developer community.')\n },\n [PackageType.OFFICIAL]: {\n label: gettext('OFFICIAL`Package maintained by Cumulocity.`'),\n tooltip: gettext('Package maintained by Cumulocity.')\n },\n [PackageType.UNKNOWN]: {\n label: gettext('CUSTOM`Package maintained by an unknown source.`'),\n tooltip: gettext('Package maintainer unknown.')\n },\n [PackageType.ARCHIVED]: {\n label: gettext('ARCHIVED`Package out of maintenance.`'),\n tooltip: gettext('The package was marked by the author as archived.')\n }\n};\n\nexport const packageProperties: PropertiesListItem[] = [\n {\n label: gettext('Latest version'),\n key: 'version'\n },\n {\n label: gettext('Author'),\n key: 'author'\n },\n {\n label: gettext('Keywords'),\n key: 'keywords'\n },\n {\n label: gettext('Source'),\n key: 'repository',\n transform: (repository: any) => (repository?.url ? repository.url : repository),\n type: 'link',\n action: (e, link: string) => window.open(link, '_blank', 'noopener,noreferrer')\n },\n {\n label: gettext('Homepage'),\n key: 'homepage',\n type: 'link',\n action: (e, link: string) => window.open(link, '_blank', 'noopener,noreferrer')\n },\n {\n label: gettext('License'),\n key: 'license'\n }\n];\n","import { ERROR_MESSAGES } from './ecosystem.constants';\nimport { ERROR_TYPE } from './ecosystem.model';\n\nexport class EcosystemError extends Error {\n constructor(public type: ERROR_TYPE) {\n super(ERROR_MESSAGES[type]);\n }\n}\n","import { EventEmitter, Injectable } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\nimport {\n ApplicationAvailability,\n ApplicationService,\n ApplicationType,\n IApplication,\n IApplicationBinary,\n IApplicationVersion,\n IApplicationVersionDeleteParams,\n ICurrentTenant,\n IdReference,\n IFetchResponse,\n IIdentified,\n IManagedObject,\n IManifest,\n InventoryService,\n IResult,\n IResultList,\n IUploadParamsOverride,\n TenantService\n} from '@c8y/client';\nimport {\n AlertService,\n ApplicationPlugin,\n AppStateService,\n gettext,\n HumanizeAppNamePipe,\n ModalService,\n PackageType,\n PluginsService,\n Status,\n WizardModalService,\n ZipService\n} from '@c8y/ngx-components';\nimport { TranslateService } from '@ngx-translate/core';\nimport { saveAs } from 'file-saver';\nimport { cloneDeep, get, groupBy, kebabCase, pick, uniqBy, omit, isUndefined } from 'lodash-es';\nimport { BehaviorSubject, Observable, defer } from 'rxjs';\nimport { debounceTime, take, map, filter, shareReplay } from 'rxjs/operators';\nimport { gt, coerce, satisfies } from 'semver';\nimport { EcosystemError } from './ecosystem-error';\nimport { ApplicationState, APP_STATE, ERROR_MESSAGES } from './ecosystem.constants';\nimport { EcosystemWizards, ERROR_TYPE, LicensedApplicationPlugin } from './ecosystem.model';\nimport { AppFilterProps } from './list-filters/list-filters.model';\n\nconst CUMULOCITY_JSON = 'cumulocity.json';\nconst MICROSERVICE_NAME_MAX_LENGTH = 23;\n\n@Injectable({\n providedIn: 'root'\n})\nexport class EcosystemService {\n appDeleted = new EventEmitter<IApplication>();\n progress: BehaviorSubject<number> = new BehaviorSubject<number>(null);\n private appsGroupedByContextPath$: Observable<Record<string, IApplication[]>>;\n private xhr: XMLHttpRequest;\n\n constructor(\n private modal: ModalService,\n private alertService: AlertService,\n private humanizeAppName: HumanizeAppNamePipe,\n private translateService: TranslateService,\n private applicationService: ApplicationService,\n private appStateService: AppStateService,\n private zipService: ZipService,\n private tenantService: TenantService,\n private inventoryService: InventoryService,\n private wizardModalService: WizardModalService,\n private pluginService: PluginsService\n ) {\n this.appsGroupedByContextPath$ = defer(() => this.getWebApplications()).pipe(\n map(webApps => groupBy(webApps, 'contextPath')),\n shareReplay({ bufferSize: 1, refCount: true })\n );\n }\n\n getUniqueAppConfig(srcApp: IApplication, existingApps: IApplication[]): IApplication {\n let app: IApplication = {\n name: srcApp.name,\n key: srcApp.key,\n contextPath: srcApp.contextPath\n };\n for (let retryNo = 0; retryNo < 9; ) {\n if (this.checkIfAppNameKeyPathExists(existingApps, app, retryNo)) {\n retryNo++;\n app = {\n name: [srcApp.name, retryNo].join('-'),\n key: [srcApp.key, retryNo].join('-'),\n contextPath: [srcApp.contextPath, retryNo].join('-')\n };\n } else {\n return app;\n }\n }\n return app;\n }\n\n /**\n * Verify versions compatibility for blueprints. If a blueprint version\n * is not compatible, a warning is shown.\n *\n * @param blueprint The blueprint to install.\n * @returns true if the installation can continue or false if it should be aborted.\n */\n async verifyBlueprintVersionsCompatibility(blueprint: Partial<IManifest>): Promise<boolean> {\n const api = await this.getPlatformVersion();\n if (blueprint.versioningMatrix) {\n try {\n const pluginApiVersion = blueprint.versioningMatrix[blueprint.version].api;\n if (!satisfies(api, pluginApiVersion)) {\n return await this.showModal(blueprint, false, api);\n }\n } catch {\n return await this.showModal(blueprint, false, api);\n }\n }\n return true;\n }\n\n /**\n * Verify versions compatibility for plugins. In case a version does not exist in the\n * versioningMatrix we don't do anything due to backward compatibility. If a plugin version\n * is not compatible, a warning is shown.\n *\n * @param pluginsToInstall The list of plugins to install.\n * @returns true if the installation can continue or false if it should be aborted.\n */\n async verifyPluginVersionsCompatibility(\n pluginsToInstall: ApplicationPlugin[],\n app: IApplication\n ): Promise<boolean> {\n const api = await this.getPlatformVersion();\n const sdk = app.manifest?.webSdkVersion;\n for (const plugin of pluginsToInstall) {\n if (plugin.versioningMatrix) {\n try {\n const pluginSdkVersion = plugin.versioningMatrix[plugin.version].sdk;\n const pluginApiVersion = plugin.versioningMatrix[plugin.version].api;\n if (!satisfies(sdk, pluginSdkVersion) || !satisfies(api, pluginApiVersion)) {\n return await this.showModal(plugin, true, api, sdk);\n }\n } catch {\n return await this.showModal(plugin, false, api, sdk);\n }\n }\n }\n\n return true;\n }\n\n /**\n * Community plugins need to verify the license agreement. If a package is a community\n * package, the license is shown.\n *\n * @param pluginsToInstall The list of plugins to install.\n * @returns true if the installation can continue.\n */\n async verifyLicenses(pluginsToInstall: Array<LicensedApplicationPlugin>) {\n let _resolve;\n const result: Promise<boolean> = new Promise(resolve => {\n _resolve = resolve;\n });\n\n pluginsToInstall = pluginsToInstall.filter(\n plugin => plugin.type !== PackageType.CUSTOM && plugin.type !== PackageType.OFFICIAL\n );\n\n if (pluginsToInstall.length === 0) {\n return Promise.resolve(true);\n }\n\n const initialState: any = {\n id: EcosystemWizards.LICENSE_CONFIRM,\n componentInitialState: {\n pluginsToInstall\n }\n };\n const modalOptions = { initialState };\n const wizard = this.wizardModalService.show(modalOptions);\n\n wizard.content.onClose.subscribe(confirmed => {\n _resolve(confirmed);\n });\n\n return result;\n }\n\n /**\n * If the plugin is archived, a warning should be shown.\n *\n * @param pluginsToInstall The list of plugins to install.\n * @returns true if the installation can continue.\n */\n async verifyArchived(pluginsToInstall: Array<LicensedApplicationPlugin>) {\n let _resolve;\n const result: Promise<boolean> = new Promise(resolve => {\n _resolve = resolve;\n });\n\n pluginsToInstall = pluginsToInstall.filter(plugin => plugin.type === PackageType.ARCHIVED);\n\n if (pluginsToInstall.length === 0) {\n return Promise.resolve(true);\n }\n\n const initialState: any = {\n id: EcosystemWizards.ARCHIVED_CONFIRM\n };\n const modalOptions = { initialState };\n const wizard = this.wizardModalService.show(modalOptions);\n\n wizard.content.onClose.subscribe(confirmed => {\n _resolve(confirmed);\n });\n\n return result;\n }\n\n /**\n * @description\n * Compares currently deployed application version with application version tagged as \"latest\"\n *\n * @param {string} currentApplicationVersion Deployed application version\n * @param {object} latestApp Latest application version object\n *\n * @returns {boolean} Returns true if latest version is greater than current, otherwise false\n */\n shouldUpgradePackage(currentApplicationVersion: string, latestApp: IApplicationVersion): boolean {\n const latestApplicationVersion = latestApp?.version;\n\n if (!latestApplicationVersion || !currentApplicationVersion) {\n return false;\n }\n\n return gt(coerce(latestApplicationVersion), coerce(currentApplicationVersion));\n }\n\n /**\n * @description\n * Gets an object that contains searched tag\n *\n * @param {array} applicationVersions Array with all available versions\n * @param {string} tagName Searched tag\n *\n * @returns {object} Returns an object with searched tag\n */\n getApplicationVersionObjectByTag(\n applicationVersions: IApplicationVersion[],\n tagName: string\n ): IApplicationVersion {\n return applicationVersions?.find(element => element.tags.includes(tagName));\n }\n\n async getApplication(appId: IdReference): Promise<IApplication> {\n return (await this.applicationService.detail(appId)).data;\n }\n\n getApplications(customFilter: any = {}): Promise<IResultList<IApplication>> {\n const filter: object = {\n pageSize: 2000,\n withTotalPages: true\n };\n Object.assign(filter, customFilter);\n const currentTenant = this.appStateService.currentTenant.value;\n return this.applicationService.listByTenant(currentTenant.name, filter);\n }\n\n async getMicroservices(): Promise<IApplication[]> {\n const apps = (await this.getApplications()).data;\n const microservices = apps.filter(app => this.isMicroservice(app));\n return microservices.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n async getWebApplications(customFilter: any = {}): Promise<IApplication[]> {\n const apps = (await this.getApplications(customFilter)).data;\n const webApps = apps.filter(app => this.isApplication(app));\n return webApps.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n async getFeatureApplications(customFilter: any = {}): Promise<IApplication[]> {\n const apps = (await this.getApplications(customFilter)).data;\n const webApps = apps.filter(app => this.isFeature(app));\n return webApps.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n async getPackageApplications(customFilter: any = {}): Promise<IApplication[]> {\n const filterCallback = app => this.isPackage(app);\n return this.getApplicationsFiltered(customFilter, filterCallback);\n }\n\n async getHostedAndPackageApplications(customFilter: any = {}): Promise<IApplication[]> {\n const filterCallback = app => this.isPackage(app) || this.isApplication(app);\n return this.getApplicationsFiltered(customFilter, filterCallback);\n }\n\n async getApplicationsFiltered(\n customFilter: any = {},\n filterCallback = (app: IApplication) => !!app\n ): Promise<IApplication[]> {\n const filter = Object.assign({}, customFilter);\n const sharedFilter = Object.assign(\n {\n availability: ApplicationAvailability.SHARED,\n type: 'HOSTED',\n pageSize: 2000\n },\n customFilter\n );\n const [{ data: apps }, { data: shared }] = await Promise.all([\n this.getApplications(filter),\n this.applicationService.list(sharedFilter)\n ]);\n const webApps = [...apps, ...shared].filter(filterCallback);\n // an app could be subscribed to a tenant, but also have it's availability set to SHARED, in that case it would occur twice.\n const uniqWebApps = uniqBy(webApps, (app: IApplication) => app.id);\n return uniqWebApps.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n async isMicroserviceHostingAllowed(): Promise<boolean> {\n const { data: apps } = await this.applicationService.listByName('feature-microservice-hosting');\n return !!apps.filter(app => app.owner?.tenant?.id === 'management').length;\n }\n\n canOpenAppInBrowser(app: IApplication): boolean {\n const isNotAFeature = !this.isFeature(app);\n const hasProperType = [ApplicationType.HOSTED, ApplicationType.EXTERNAL].includes(app.type);\n const isNotPackage = !this.isPackage(app);\n return isNotAFeature && hasProperType && isNotPackage;\n }\n\n openApp(app) {\n window.open(this.applicationService.getHref(app), '_blank', 'noopener,noreferrer');\n }\n\n async canDeleteApp(app: IApplication): Promise<boolean> {\n return (\n this.isOwner(app) && (!this.isCurrentApp(app) || (await this.hasSubscribedAppParent(app)))\n );\n }\n\n isOwner(app: IApplication): boolean {\n const currentTenant: ICurrentTenant = this.appStateService.currentTenant.value;\n const appOwner = get(app, 'owner.tenant.id');\n return currentTenant?.name === appOwner;\n }\n\n isFeature(app: IApplication): boolean {\n return !!app.name.match(/feature-/);\n }\n\n isMicroservice(app: IApplication): boolean {\n return app.type === 'MICROSERVICE';\n }\n\n isExternal(app: IApplication): boolean {\n return app.type === 'EXTERNAL';\n }\n\n isPackage(app: IApplication): boolean {\n return app.manifest?.isPackage === true;\n }\n\n isPlugin(app: IApplication): boolean {\n return app.manifest?.package === 'plugin';\n }\n\n cancelAppCreation(app: IApplication): void {\n if (this.xhr) {\n this.xhr.abort();\n }\n if (app) {\n this.applicationService.delete(app);\n }\n }\n\n updateUploadProgress(event): void {\n if (event.lengthComputable) {\n const currentProgress = this.progress.value;\n this.progress.next(currentProgress + (event.loaded / event.total) * (95 - currentProgress));\n }\n }\n\n setAppActiveVersion(app: IApplication, activeVersionId: string): Promise<IApplication> {\n return this.applicationService.update({ id: app.id, activeVersionId });\n }\n\n setPackageVersionTag(app: IApplication, version: string, tags: string[]) {\n return this.applicationService.setPackageVersionTag(app, version, tags);\n }\n\n deletePackageVersion(app: IApplication, params: IApplicationVersionDeleteParams) {\n return this.applicationService.deleteVersionPackage(app, params);\n }\n\n getHumanizedAppName(app: IApplication): Promise<string> {\n return this.humanizeAppName.transform(app.name).pipe(debounceTime(250), take(1)).toPromise();\n }\n\n createConfig(app: IApplication, formGroupValue: FormGroup): Partial<IApplication> {\n const { id, type, availability } = app;\n let config = pick(formGroupValue, ['name', 'key', 'contextPath']);\n config = {\n ...config,\n isSetup: true,\n id,\n type,\n availability\n };\n return config;\n }\n\n async listArchives(appId: string | number | IApplication): Promise<IApplicationBinary[]> {\n const filter = {\n pageSize: 100\n };\n return (await this.applicationService.binary(appId).list(filter)).data;\n }\n\n async deleteArchive(archive: IApplicationBinary, app: IApplication): Promise<void> {\n const humanizedArchiveName = await this.getHumanizedAppName(archive);\n try {\n await this.modal.confirm(\n gettext('Delete archive'),\n this.translateService.instant(\n gettext(\n `You are about to delete archive \"{{ humanizedArchiveName }}\". Do you want to proceed?`\n ),\n { humanizedArchiveName }\n ),\n Status.DANGER,\n { ok: gettext('Delete'), cancel: gettext('Cancel') }\n );\n await this.applicationService.binary(app).delete(archive.id);\n this.alertService.success(gettext('Archive deleted.'));\n } catch (ex) {\n if (ex) {\n this.alertService.danger(get(ex, 'data.message'), ex.data);\n }\n throw new Error('Cancelled');\n }\n }\n\n async getArchiveManagedObject(binaryId: string): Promise<IManagedObject> {\n return (await this.inventoryService.detail(binaryId)).data;\n }\n\n async downloadArchive(\n app: IApplication,\n archive: Pick<IApplicationBinary, 'name' | 'id'>\n ): Promise<void> {\n try {\n const binary = await this.getBinary(app, archive);\n const fileBinary = new Blob([binary], { type: 'application/x-zip-compressed' });\n saveAs(fileBinary, archive.name);\n } catch (e) {\n // empty\n }\n }\n\n async updateApp(app: IApplication, deleteOnFailure = false): Promise<IResult<IApplication>> {\n try {\n return await this.applicationService.update(app);\n } catch (ex) {\n this.alertError(ex);\n if (deleteOnFailure) {\n await this.applicationService.delete(app.id);\n throw new EcosystemError(ERROR_TYPE.APPLICATION_CREATION_FAILED);\n }\n }\n }\n\n async deleteApp(app: IApplication, silent = false): Promise<void> {\n const humanizedAppName = await this.getHumanizedAppName(app);\n if (!silent) {\n await this.modal.confirm(\n gettext('Delete application'),\n this.translateService.instant(\n gettext(\n `You are about to delete application \"{{ humanizedAppName }}\". Do you want to proceed?`\n ),\n { humanizedAppName }\n ),\n Status.DANGER,\n { ok: gettext('Delete'), cancel: gettext('Cancel') }\n );\n }\n await this.applicationService.delete(app.id);\n if (!silent) {\n this.alertService.success(gettext('Application deleted.'));\n }\n this.appDeleted.emit(app);\n }\n\n async checkIfSubscribed(app: IApplication): Promise<boolean> {\n const currentTenant = await this.tenantService.current();\n const subscribedApps = currentTenant.data.applications.references;\n return subscribedApps.some(application => application.application.id === app.id);\n }\n\n async subscribeApp(app: IApplication): Promise<void> {\n const currentTenant: ICurrentTenant = this.appStateService.currentTenant.value;\n try {\n await this.tenantService.subscribeApplication(currentTenant, app);\n this.alertService.success(gettext('Successfully subscribed to application.'));\n } catch (ex) {\n this.alertError(ex);\n }\n }\n\n async unsubscribeApp(app: IApplication): Promise<void> {\n const currentTenant: ICurrentTenant = this.appStateService.currentTenant.value;\n try {\n await this.tenantService.unsubscribeApplication(currentTenant, app);\n this.alertService.success(gettext('Successfully unsubscribed from application.'));\n } catch (ex) {\n this.alertError(ex);\n }\n }\n\n async isValidAppType(archive: File, appType: ApplicationType): Promise<boolean> {\n try {\n const currentType = await this.getAppType(archive);\n if (currentType !== appType) {\n throw new EcosystemError(ERROR_TYPE.TYPE_VALIDATION);\n } else {\n this.progress.next(this.progress.value + 10);\n return true;\n }\n } catch (ex) {\n throw new EcosystemError(ERROR_TYPE.TYPE_VALIDATION);\n }\n }\n\n async uploadArchiveToApp(\n archive: File,\n app: IApplication,\n isNewVersion = false\n ): Promise<IApplication> {\n let uploadOverrides: IUploadParamsOverride;\n if (isNewVersion) {\n uploadOverrides = await this.getUploadOverrides(archive, app);\n }\n const binaryService = this.applicationService.binary(app);\n this.xhr = binaryService.uploadWithProgressXhr(\n archive,\n this.updateUploadProgress.bind(this),\n '',\n uploadOverrides\n );\n\n const binaryMo = await binaryService.getXMLHttpResponse(this.xhr);\n // TODO commented it due to: https://cumulocity.atlassian.net/browse/MTM-48553\n // Add it back when BE fixes issues with activeVersion.\n // if (isNewVersion) {\n // return await this.getApplication(app);\n // }\n const notInitialPackage = app.applicationVersions?.length > 0;\n if (notInitialPackage) {\n return (await this.applicationService.update({ id: app.id })).data;\n }\n return (await this.setAppActiveVersion(app, (binaryMo.binaryId || binaryMo.id) as string)).data;\n }\n\n async validateArchiveToAppCompatibility(archive: File, app: IApplication): Promise<void> {\n const appType = await this.getAppType(archive);\n if (appType !== app.type) {\n throw new EcosystemError(ERROR_TYPE.INVALID_APPLICATION);\n } else {\n this.progress.next(this.progress.value + 10);\n }\n if (this.isMicroservice(app)) {\n return;\n }\n const manifest = await this.getCumulocityJson(archive);\n // A user can upload an app without a Cumulocity JSON file (e.g. a react app).\n // This is allowed and should not trigger a validation error.\n if (!manifest) {\n return;\n }\n await this.validatePackageKeyAndContextPath(manifest, app);\n }\n\n async getCumulocityJson(archive: File): Promise<IManifest | null> {\n try {\n const c8yManifest = await this.getCumulocityJson$(archive).toPromise();\n return c8yManifest;\n } catch (ex) {\n return null;\n }\n }\n\n async createAppForArchive(archive, isPackageTypeArchive = false): Promise<IApplication> {\n let isPackage = false;\n const appType = await this.getAppType(archive);\n let appModel: any = {};\n const supportedAppTypes = [ApplicationType.HOSTED, ApplicationType.MICROSERVICE];\n\n if (supportedAppTypes.includes(appType)) {\n try {\n appModel = await this.getCumulocityJson$(archive).toPromise();\n isPackage = appModel.isPackage;\n } catch (e) {\n // do nothing, we allow having HOSTED applications without the manifest file\n }\n }\n const name = this.getBaseNameFromArchiveOrAppModel(archive, appType, appModel);\n const clearedName = this.removeForbiddenCharacters(name);\n const key = this.getAppKey(appModel, clearedName);\n const contextPath = this.getContextPath(appModel, name);\n\n const appToSave = {\n type: appType,\n name,\n key,\n contextPath\n };\n\n if (isPackageTypeArchive && !isPackage) {\n throw new EcosystemError(ERROR_TYPE.INVALID_PACKAGE);\n } else if (!isPackageTypeArchive && isPackage) {\n throw new EcosystemError(ERROR_TYPE.INVALID_APPLICATION);\n } else if (this.isNameLengthExceeded(name, appType)) {\n const error = new Error();\n error.name = ERROR_TYPE.MICROSERVICE_NAME_TOO_LONG;\n error.message = this.translateService.instant(ERROR_MESSAGES[error.name], {\n name,\n maxChars: MICROSERVICE_NAME_MAX_LENGTH\n });\n throw error;\n }\n return (\n await this.applicationService.create({\n ...appToSave,\n manifest: {\n isPackage,\n ...(appModel?.package && { package: appModel.package })\n } as unknown as IManifest\n })\n ).data;\n }\n\n async reactivateArchive(app: IApplication): Promise<void> {\n try {\n await this.applicationService.reactivateArchive(app.id);\n this.alertService.success(gettext('Application reactivated.'));\n } catch (ex) {\n this.alertError(ex);\n }\n }\n\n async removeOldestArchive(app: IApplication, archives: IApplicationBinary[]): Promise<void> {\n try {\n await this.modal.confirm(\n gettext('Delete oldest archive and continue'),\n gettext(\n '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?'\n ),\n Status.INFO,\n { ok: gettext('Delete and continue') }\n );\n const archiveToDelete = archives[archives.length - 2];\n await this.applicationService.binary(app).delete(archiveToDelete.id);\n this.alertService.success(gettext('Archive deleted.'));\n } catch (ex) {\n if (ex) {\n this.alertError(ex);\n } else {\n return Promise.reject('cancelled');\n }\n }\n }\n\n async deployApp(selectedPackage: IApplication, formGroupValue, model): Promise<IApplication> {\n // Create new app config\n const config = this.createConfig(selectedPackage, formGroupValue);\n const requestedVersion = model.selected.version;\n let cleanManifest;\n try {\n const manifest = await this.applicationService.getAppManifest(\n selectedPackage,\n requestedVersion\n );\n cleanManifest = omit(manifest, ['name', 'contextPath', 'key']);\n } catch (ex) {\n throw new EcosystemError(ERROR_TYPE.VERSION_NOT_FOUND);\n }\n\n config.isSetup = true;\n config.manifest = cleanManifest;\n config.availability = ApplicationAvailability.PRIVATE;\n config.manifest.isPackage = false;\n config.manifest.source = selectedPackage.id;\n config.manifest.package = 'blueprint';\n\n // because of a issue with SHARED availability we always should check again\n // if the app not exist already\n const allExistingApps = await this.getHostedAndPackageApplications();\n const doesAppKeyOrContextPathExist = this.checkIfAppNameKeyPathExists(allExistingApps, config);\n if (doesAppKeyOrContextPathExist) {\n throw new EcosystemError(ERROR_TYPE.ALREADY_EXIST);\n }\n\n // Create new app\n const newApp = (await this.applicationService.create(config)).data;\n try {\n // Binary upload can fail if SHARED package\n // in this case, catch error and fall back to\n // clone API.\n await this.uploadBinaryFromOtherPackage(\n selectedPackage,\n newApp,\n model.selected.binaryId,\n requestedVersion\n );\n } catch (error) {\n if (error?.res?.status === 404) {\n await this.fallbackToClone(newApp, selectedPackage, requestedVersion);\n }\n }\n\n // update the icon if a custom one is selected\n if (formGroupValue.icon) {\n await this.applicationService.update({\n id: newApp.id,\n config: { icon: formGroupValue.icon }\n });\n }\n return newApp;\n }\n\n async fallbackToClone(\n application: IApplication,\n selectedPackage: IApplication,\n requestedVersion?: string\n ) {\n let wasSuccess = true;\n let clonedPkg;\n try {\n clonedPkg = (await this.applicationService.clone(selectedPackage, requestedVersion)).data;\n await this.uploadBinaryFromOtherPackage(\n selectedPackage,\n application,\n clonedPkg.activeVersionId,\n requestedVersion,\n clonedPkg\n );\n } catch (error) {\n this.alertError(error);\n wasSuccess = false;\n } finally {\n await this.deleteApp(clonedPkg, true);\n }\n return wasSuccess;\n }\n\n async uploadBinaryFromOtherPackage(\n selectedPackage: IApplication,\n applicationToUploadBinaryTo: IApplication,\n binaryId: string,\n requestedVersion?: string,\n useBinariesFrom?: IApplication\n ) {\n const { data: binaryDetails } = await this.inventoryService.detail(binaryId);\n\n // Get binary from specific package version\n const binary = await this.getBinary(useBinariesFrom || selectedPackage, {\n id: binaryId\n });\n // Create zip\n const fileBinary = new Blob([binary], { type: binaryDetails.contentType });\n const file = new File([fileBinary], binaryDetails.name, {\n type: binaryDetails.contentType\n });\n\n // Upload binary to new app\n await this.uploadArchiveToApp(file, applicationToUploadBinaryTo);\n\n // update the app manifest\n await this.updateAppManifest(applicationToUploadBinaryTo, selectedPackage, requestedVersion);\n }\n\n getAppState(app: IApplication): ApplicationState {\n if (!this.isOwner(app)) {\n return APP_STATE.SUBSCRIBED;\n } else if (this.isUnpacked(app)) {\n return APP_STATE.UNPACKED;\n } else if (app.type === ApplicationType.EXTERNAL) {\n return APP_STATE.EXTERNAL;\n }\n return APP_STATE.CUSTOM;\n }\n\n getPackageContentState(app: IApplication): ApplicationState {\n if (!this.isPackage(app)) {\n return;\n }\n if (this.isPackageBlueprint(app)) {\n return APP_STATE.PACKAGE_BLUEPRINT;\n }\n if (this.isPluginsPackage(app)) {\n return APP_STATE.PACKAGE_PLUGIN;\n }\n return APP_STATE.PACKAGE_UNKNOWN;\n }\n\n isPackageBlueprint(app: IApplication): boolean {\n return this.isPackage(app) && app.manifest.package === 'blueprint';\n }\n\n isPluginsPackage(app: IApplication): boolean {\n return this.isPackage(app) && app.manifest.package === 'plugin';\n }\n\n isUnpacked(app: IApplication): boolean {\n return !!app.manifest?.source;\n }\n\n hasExports(app: IApplication): boolean {\n return !!app.manifest?.exports?.length;\n }\n\n isApplication(app: IApplication): boolean {\n return (\n app.type !== ApplicationType.MICROSERVICE && !this.isFeature(app) && !this.isPackage(app)\n );\n }\n\n isCustomMicroservice(app: IApplication) {\n return this.isOwner(app) && app.type === ApplicationType.MICROSERVICE;\n }\n\n async getBinary(\n app: IApplication,\n archive: IApplicationBinary | IIdentified\n ): Promise<ArrayBuffer> {\n let binary;\n try {\n const res = await this.applicationService.binary(app).downloadArchive(archive.id);\n binary = await res.arrayBuffer();\n } catch (ex) {\n const msg = gettext('Could not get the binary.');\n this.alertService.danger(msg);\n }\n return binary;\n }\n\n async isOverwrittenByCustomApp(app: IApplication): Promise<boolean> {\n return !this.isOwner(app) && (await this.hasSubscribedAppParent(app));\n }\n\n async hasSubscribedAppParent(app: IApplication): Promise<boolean> {\n const appsGroupedByContextPath = await this.appsGroupedByContextPath$.pipe(take(1)).toPromise();\n return app.contextPath && appsGroupedByContextPath[app.contextPath]?.length === 2;\n }\n\n /**\n * @deprecated\n */\n setAvailabilityToPrivateIfNotSetAlready(app: IApplication): IApplication {\n app.availability = ApplicationAvailability.PRIVATE;\n return app;\n }\n\n /**\n * Shows an error dialog.\n * @param error Either a server error or an internal [[EcosystemError]].\n */\n alertError(error: Error | EcosystemError) {\n if (error instanceof EcosystemError) {\n this.alertService.danger(error.message);\n } else {\n this.alertService.addServerFailure(error);\n }\n }\n\n async validatePackageKeyAndContextPath(manifest: IManifest, app: IApplication) {\n const contextPath = get(manifest, 'contextPath');\n const appKey = get(manifest, 'key');\n if (contextPath !== app.contextPath || appKey !== app.key) {\n throw new EcosystemError(ERROR_TYPE.KEY_OR_CONTEXT_PATH_MISMATCH);\n }\n }\n\n filterContainString(name: string, filterTerm: string): boolean {\n const term = filterTerm.toLowerCase().trim();\n return name && name.toLowerCase().indexOf(term) > -1;\n }\n\n async updateAppManifest(\n application: IApplication,\n selectedPackage: IApplication,\n requestedVersion?: string\n ): Promise<{ res: IFetchResponse; data: any }> {\n const { id } = application;\n const cleanedApp = this.removeAppProperties(application);\n let manifest: Partial<IManifest> = selectedPackage.manifest || {};\n if (requestedVersion) {\n try {\n const versionedAppManifest = await this.applicationService.getAppManifest(\n selectedPackage,\n requestedVersion\n );\n manifest = versionedAppManifest;\n } catch (ex) {\n throw new EcosystemError(ERROR_TYPE.VERSION_NOT_FOUND);\n }\n }\n\n cleanedApp.manifest = manifest;\n cleanedApp.manifest.isPackage = false;\n cleanedApp.manifest.source = selectedPackage.id;\n\n return await this.applicationService\n .binary(id)\n .updateFiles([{ path: CUMULOCITY_JSON, contents: JSON.stringify(cleanedApp) }]);\n }\n\n /**\n * Creates object containing properties for filtering applications on lists.\n *\n * @param {object} app Application to create filter properties from.\n * @returns {object} Properties to filter by applications list.\n */\n getAppFilterProps(app: IApplication): AppFilterProps {\n return {\n type: this.pluginService.getPackageType(app),\n availability: this.getAppState(app)?.label,\n content: this.getPackageContentState(app)?.label\n };\n }\n\n private checkIfAppNameKeyPathExists(\n existingApps: IApplication[],\n app: IApplication,\n retryNo?: number\n ): IApplication {\n if (isUndefined(retryNo)) {\n return existingApps.find(\n existingApp =>\n existingApp.name === app.name ||\n existingApp.key === app.key ||\n existingApp.contextPath === app.contextPath\n );\n }\n return existingApps.find(\n existingApp =>\n existingApp.name === app.name ||\n existingApp.key === app.key ||\n existingApp.contextPath === app.contextPath ||\n existingApp.name === [app.name, retryNo].join('-') ||\n existingApp.key === [app.key, retryNo].join('-') ||\n existingApp.contextPath === [app.contextPath, retryNo].join('-')\n );\n }\n\n private async showModal(\n packageType: Partial<IManifest> | ApplicationPlugin,\n isPlugin: boolean,\n apiVersion: string,\n sdkVersion?: string\n ): Promise<boolean> {\n try {\n const messages = {\n plugin: {\n title: gettext('Plugin installation'),\n body: sdkVersion\n ? gettext(\n '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?'\n )\n : gettext(\n '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?'\n )\n },\n blueprint: {\n title: gettext('Blueprint installation'),\n body: sdkVersion\n ? gettext(\n '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?'\n )\n : gettext(\n '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?'\n )\n }\n };\n const entityType = isPlugin ? 'plugin' : 'blueprint';\n const selectedMessages = messages[entityType];\n const translatedBody = this.translateService.instant(selectedMessages.body, {\n name: packageType.name,\n apiVersion,\n sdkVersion\n });\n await this.modal.confirm(selectedMessages.title, translatedBody, 'warning', {\n ok: gettext('Continue'),\n cancel: gettext('Cancel')\n });\n } catch {\n // modal canceled\n return false;\n }\n\n return true;\n }\n\n private async getPlatformVersion() {\n return await this.appStateService.state$\n .pipe(\n take(1),\n map(state => state?.versions?.backend),\n filter(backendVersion => !!backendVersion)\n )\n .toPromise();\n }\n\n private getAppKey(appModel: IApplication, name: string): string {\n let key = appModel?.key;\n if (!key) {\n key = `${kebabCase(name)}-key`;\n }\n return key;\n }\n\n private getContextPath(appModel: IApplication, name: string): string {\n return appModel?.contextPath || name.toLowerCase();\n }\n\n private removeForbiddenCharacters(str: string): string {\n return str.replace(/[^a-zA-Z0-9-_]/g, '');\n }\n\n private isCurrentApp(app: IApplication): boolean {\n const currentApp = this.appStateService.state.app;\n return currentApp.contextPath === app.contextPath;\n }\n\n private getCumulocityJson$(archive: File): Observable<any> {\n return this.zipService.getJsonData(archive, {\n filename: CUMULOCITY_JSON\n });\n }\n\n private getAppType(archive: File): Promise<ApplicationType> {\n return this.getCumulocityJson$(archive)\n .toPromise()\n .then(\n data =>\n get(data, 'type') ||\n (get(data, 'apiVersion') ? ApplicationType.MICROSERVICE : ApplicationType.HOSTED)\n )\n .catch(() => ApplicationType.HOSTED);\n }\n\n private getBaseNameFromArchiveOrAppModel(\n archive: any,\n appType: ApplicationType,\n appModel?: IApplication\n ): string {\n let baseName = appModel?.name || archive.name.replace(/\\.zip$/i, '');\n if (appType === 'MICROSERVICE') {\n baseName = this.removeVersionFromName(baseName);\n }\n return baseName;\n }\n\n private removeAppProperties(app: IApplication): IApplication {\n const tempApp = cloneDeep(app);\n const propertiesToRemove = ['id', 'owner', 'activeVersionId', 'self', 'type'];\n\n propertiesToRemove.forEach(prop => delete tempApp[prop]);\n return tempApp;\n }\n\n private async getUploadOverrides(\n archive: File,\n app: IApplication\n ): Promise<IUploadParamsOverride> {\n const { version } = await this.getCumulocityJson$(archive).toPromise();\n const isInitialPackage = app.applicationVersions?.length === 0;\n return {\n listUrl: 'versions',\n headers: {\n Accept: 'application/vnd.com.nsn.cumulocity.applicationVersion+json;charset=UTF-8;ver=0.9'\n },\n bodyFileProperty: 'applicationBinary',\n requestBody: {\n applicationVersion: { version, ...(isInitialPackage && { tags: ['latest'] }) }\n }\n };\n }\n\n private removeVersionFromName(name: string) {\n const versionRegExp = /-\\d+\\.\\d+\\.\\d+(\\.\\d+)?(-\\d+)?(.*)$/;\n return name.replace(versionRegExp, '');\n }\n\n private isNameLengthExceeded(name, appType) {\n return name.length > MICROSERVICE_NAME_MAX_LENGTH && appType === ApplicationType.MICROSERVICE;\n }\n}\n","import { Injectable } from '@angular/core';\nimport { ApplicationAvailability, ApplicationService, IApplication } from '@c8y/client';\nimport { TranslateService } from '@ngx-translate/core';\nimport {\n AlertService,\n AppStateService,\n GainsightService,\n ModalService,\n gettext\n} from '@c8y/ngx-components';\nimport { PRODUCT_EXPERIENCE_ECOSYSTEM } from './ecosystem.model';\n\n@Injectable({ providedIn: 'root' })\nexport class PackageAvailabilityService {\n CURRENT_LOCATION = location.href;\n availabilities = [\n { label: gettext('Private`package availability`'), value: ApplicationAvailability.PRIVATE },\n { label: gettext('Market`package availability`'), value: ApplicationAvailability.MARKET },\n { label: gettext('Shared`package availability`'), value: ApplicationAvailability.SHARED }\n ];\n\n constructor(\n private appState: AppStateService,\n private alert: AlertService,\n private modal: ModalService,\n private application: ApplicationService,\n private gainsightService: GainsightService,\n private translateService: TranslateService\n ) {}\n\n async askIfAvailabilityShouldBeSetTo(\n applicationPackage: IApplication,\n availability: ApplicationAvailability\n ): Promise<IApplication> {\n // availability does not matter for tenant that do not have or can create subtenants\n if (!this.appState.currentTenant.value?.allowCreateTenants) {\n return applicationPackage;\n }\n try {\n await this.openAvailabilityModal(availability);\n // user confirmed changing availability\n return await this.setAvailability(applicationPackage, availability);\n } catch {\n // user canceled changing availability\n return applicationPackage;\n }\n }\n\n async setAvailability(\n applicationPackage: IApplication,\n availability: ApplicationAvailability\n ): Promise<IApplication> {\n try {\n const { data: app } = await this.application.updateApplicationAvailability(\n applicationPackage,\n availability\n );\n this.alert.success(gettext('Updated package availability.'));\n this.gainsightService.triggerEvent(\n PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.EVENTS.AVAILABILITY,\n {\n action: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.ACTIONS.AVAILABILITY_CHANGE,\n result: availability.toString().toLocaleLowerCase(),\n url: this.CURRENT_LOCATION\n }\n );\n return app;\n } catch (e) {\n this.alert.warning(gettext('Failed