From d1a07d782857765c4a276fe8af26066a13a17416 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Wed, 5 Feb 2025 22:50:06 +0100 Subject: [PATCH] chore(marketplace): move installstatus from plugin to package Signed-off-by: Christoph Jerolimov --- .../src/module.ts | 8 +- ...amicPackageInstallStatusProcessor.test.ts} | 24 ++-- ...> DynamicPackageInstallStatusProcessor.ts} | 8 +- ...ocalPackageInstallStatusProcessor.test.ts} | 111 ++++++++---------- ... => LocalPackageInstallStatusProcessor.ts} | 61 +++------- .../src/processors/index.ts | 4 +- .../plugins/marketplace-common/src/types.ts | 4 +- 7 files changed, 88 insertions(+), 132 deletions(-) rename workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/{DynamicPluginInstallStatusProcessor.test.ts => DynamicPackageInstallStatusProcessor.test.ts} (89%) rename workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/{DynamicPluginInstallStatusProcessor.ts => DynamicPackageInstallStatusProcessor.ts} (94%) rename workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/{LocalPluginInstallStatusProcessor.test.ts => LocalPackageInstallStatusProcessor.test.ts} (53%) rename workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/{LocalPluginInstallStatusProcessor.ts => LocalPackageInstallStatusProcessor.ts} (70%) diff --git a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/module.ts b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/module.ts index 78d05b244..80ef991f8 100644 --- a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/module.ts +++ b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/module.ts @@ -22,8 +22,8 @@ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/ import { MarketplacePluginProcessor } from './processors/MarketplacePluginProcessor'; import { MarketplacePluginListProcessor } from './processors/MarketplacePluginListProcessor'; -import { DynamicPluginInstallStatusProcessor } from './processors/DynamicPluginInstallStatusProcessor'; -import { LocalPluginInstallStatusProcessor } from './processors/LocalPluginInstallStatusProcessor'; +import { DynamicPackageInstallStatusProcessor } from './processors/DynamicPackageInstallStatusProcessor'; +import { LocalPackageInstallStatusProcessor } from './processors/LocalPackageInstallStatusProcessor'; import { MarketplacePackageProcessor } from './processors/MarketplacePackageProcessor'; /** @@ -44,10 +44,10 @@ export const catalogModuleMarketplace = createBackendModule({ logger.info('Adding Marketplace processors to catalog...'); catalog.addProcessor(new MarketplacePluginProcessor()); catalog.addProcessor(new MarketplacePluginListProcessor()); - catalog.addProcessor(new LocalPluginInstallStatusProcessor()); + catalog.addProcessor(new LocalPackageInstallStatusProcessor()); catalog.addProcessor(new MarketplacePackageProcessor()); catalog.addProcessor( - new DynamicPluginInstallStatusProcessor(discovery, auth), + new DynamicPackageInstallStatusProcessor(discovery, auth), ); }, }); diff --git a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPluginInstallStatusProcessor.test.ts b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPackageInstallStatusProcessor.test.ts similarity index 89% rename from workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPluginInstallStatusProcessor.test.ts rename to workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPackageInstallStatusProcessor.test.ts index e3e42187c..89a63ea4f 100644 --- a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPluginInstallStatusProcessor.test.ts +++ b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPackageInstallStatusProcessor.test.ts @@ -21,7 +21,7 @@ import { MarketplacePlugin, } from '@red-hat-developer-hub/backstage-plugin-marketplace-common'; -import { DynamicPluginInstallStatusProcessor } from './DynamicPluginInstallStatusProcessor'; +import { DynamicPackageInstallStatusProcessor } from './DynamicPackageInstallStatusProcessor'; const pluginEntity: MarketplacePlugin = { apiVersion: 'marketplace.backstage.io/v1alpha1', @@ -70,19 +70,19 @@ const locationSpec = { target: '', }; -describe('DynamicPluginInstallStatusProcessor', () => { +describe('DynamicPackageInstallStatusProcessor', () => { beforeEach(() => { global.fetch = jest.fn().mockResolvedValue({} as any); }); it('should return processor name', () => { - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( mockServices.discovery.mock(), authService, ); expect(processor.getProcessorName()).toBe( - 'DynamicPluginInstallStatusProcessor', + 'DynamicPackageInstallStatusProcessor', ); }); @@ -94,7 +94,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { new Response(JSON.stringify(pluginsMock), { status: 200 }), ); - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -109,7 +109,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { }); it('should handle non-200 responses gracefully', async () => { - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -129,7 +129,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { }; (cache.get as jest.Mock).mockResolvedValue(cachedData); - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -151,7 +151,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { new Response(JSON.stringify(pluginsMock), { status: 200 }), ); - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -172,7 +172,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { }); it('should not process if the installStatus is already set', async () => { - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -201,7 +201,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { new Response(JSON.stringify(pluginsMock), { status: 200 }), ); - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -229,7 +229,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { new Response(JSON.stringify(pluginsMock), { status: 200 }), ); - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); @@ -252,7 +252,7 @@ describe('DynamicPluginInstallStatusProcessor', () => { spec: {}, }; - const processor = new DynamicPluginInstallStatusProcessor( + const processor = new DynamicPackageInstallStatusProcessor( discoveryService, authService, ); diff --git a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPluginInstallStatusProcessor.ts b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPackageInstallStatusProcessor.ts similarity index 94% rename from workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPluginInstallStatusProcessor.ts rename to workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPackageInstallStatusProcessor.ts index 4b7c967f6..aebff5420 100644 --- a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPluginInstallStatusProcessor.ts +++ b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/DynamicPackageInstallStatusProcessor.ts @@ -42,7 +42,7 @@ export type CachedData = { /** * @public */ -export class DynamicPluginInstallStatusProcessor implements CatalogProcessor { +export class DynamicPackageInstallStatusProcessor implements CatalogProcessor { private discovery: DiscoveryService; private auth: AuthService; private readonly cacheTTLMilliseconds = durationToMilliseconds({ @@ -56,13 +56,13 @@ export class DynamicPluginInstallStatusProcessor implements CatalogProcessor { // Return processor name getProcessorName(): string { - return 'DynamicPluginInstallStatusProcessor'; + return 'DynamicPackageInstallStatusProcessor'; } async getInstalledPlugins() { const scalprumUrl = await this.discovery.getBaseUrl('scalprum'); - const token = await this.auth.getPluginRequestToken({ + const { token } = await this.auth.getPluginRequestToken({ onBehalfOf: await this.auth.getOwnServiceCredentials(), targetPluginId: 'catalog', }); @@ -118,7 +118,7 @@ export class DynamicPluginInstallStatusProcessor implements CatalogProcessor { ): Promise { if ( entity.apiVersion === MARKETPLACE_API_VERSION && - entity.kind === MarketplaceKind.Plugin + entity.kind === MarketplaceKind.Package ) { if (entity.spec?.installStatus === InstallStatus.Installed) { return entity; diff --git a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPluginInstallStatusProcessor.test.ts b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPackageInstallStatusProcessor.test.ts similarity index 53% rename from workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPluginInstallStatusProcessor.test.ts rename to workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPackageInstallStatusProcessor.test.ts index 0c2564223..12359e69e 100644 --- a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPluginInstallStatusProcessor.test.ts +++ b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPackageInstallStatusProcessor.test.ts @@ -1,5 +1,5 @@ /* - * Copyright Red Hat, Inc. + * Copyright The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,165 +16,154 @@ import { InstallStatus, - MarketplacePlugin, + MarketplacePackage, } from '@red-hat-developer-hub/backstage-plugin-marketplace-common'; -import { LocalPluginInstallStatusProcessor } from './LocalPluginInstallStatusProcessor'; +import { LocalPackageInstallStatusProcessor } from './LocalPackageInstallStatusProcessor'; -const pluginEntity: MarketplacePlugin = { +const packageEntity: MarketplacePackage = { apiVersion: 'marketplace.backstage.io/v1alpha1', metadata: { - name: 'testplugin', - title: 'APIs with Test plugin', - description: 'Test plugin.', + name: 'testpackage', + title: 'APIs with Test package', + description: 'Test package.', tags: ['3scale', 'api'], }, - kind: 'Plugin', + kind: 'Package', spec: { categories: ['API Discovery'], developer: 'Red Hat', - icon: 'https://janus-idp.io/images/plugins/3scale.svg', - type: 'frontend-plugin', + icon: 'https://janus-idp.io/images/packages/3scale.svg', + type: 'frontend-package', lifecycle: 'production', owner: 'test-group', - description: 'Test plugin', + description: 'Test package', installation: { - markdown: '# Installation \n run `yarn add test-plugin`', + markdown: '# Installation \n run `yarn add test-package`', }, }, }; -describe('LocalPluginInstallStatusProcessor', () => { +describe('LocalPackageInstallStatusProcessor', () => { it('should return processor name', () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); expect(processor.getProcessorName()).toBe( - 'LocalPluginInstallStatusProcessor', + 'LocalPackageInstallStatusProcessor', ); }); it('should return the workspace path', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); - const result = await processor.preProcessEntity(pluginEntity); + const result = await processor.preProcessEntity(packageEntity); expect(result?.spec?.installStatus).toBe(InstallStatus.NotInstalled); }); it('should return not installed status', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); - const result = await processor.preProcessEntity(pluginEntity); + const result = await processor.preProcessEntity(packageEntity); expect(result?.spec?.installStatus).toBe(InstallStatus.NotInstalled); }); it('should return empty string if the root folder does not have workspaces', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); const result = processor.findWorkspacesPath('../../../../../../../'); expect(result).toBe(''); }); it('should return notInstalled status if the entity does not have package version information', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); - const result = await processor.preProcessEntity(pluginEntity); + const result = await processor.preProcessEntity(packageEntity); expect(result?.spec?.installStatus).toBe(InstallStatus.NotInstalled); }); it('should return NotInstalled status if the entity has incorrect package installed', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); - const searchBackendPlugin = { - ...pluginEntity, + const searchBackendPackage = { + ...packageEntity, spec: { - ...pluginEntity.spec, + ...packageEntity.spec, packages: [ { - name: '@backstage/plugin-search-backend', + name: '@backstage/package-search-backend', version: '^2.0.1', }, ], }, }; - const result = await processor.preProcessEntity(searchBackendPlugin); + const result = await processor.preProcessEntity(searchBackendPackage); expect(result?.spec?.installStatus).toBe(InstallStatus.NotInstalled); }); it('should return Installed status if the entity has incorrect package version installed', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); - const searchBackendPlugin = { - ...pluginEntity, + const searchBackendPackage = { + ...packageEntity, spec: { - ...pluginEntity.spec, + ...packageEntity.spec, packages: [ { - name: '@backstage/plugin-search-backend', + name: '@backstage/package-search-backend', version: '^1.0.1', }, ], }, }; - const result = await processor.preProcessEntity(searchBackendPlugin); + const result = await processor.preProcessEntity(searchBackendPackage); expect(result?.spec?.installStatus).toBe(InstallStatus.Installed); }); it('should return NotInstalled status when invalid workspaces paths', async () => { - const processor = new LocalPluginInstallStatusProcessor([ + const processor = new LocalPackageInstallStatusProcessor([ 'packages/modules', ]); - const searchPlugin = { - ...pluginEntity, + const searchPackage = { + ...packageEntity, spec: { - ...pluginEntity.spec, + ...packageEntity.spec, packages: [ { - name: '@backstage/plugin-search-backend', + name: '@backstage/package-search-backend', version: '^1.0.0 , ^1.0.0', }, ], }, }; - const result = await processor.preProcessEntity(searchPlugin); + const result = await processor.preProcessEntity(searchPackage); expect(result?.spec?.installStatus).toBe(InstallStatus.NotInstalled); }); it('should return Installed status when only package names are passed', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + const processor = new LocalPackageInstallStatusProcessor(); - const searchPlugin = { - ...pluginEntity, + const searchPackage = { + ...packageEntity, spec: { - ...pluginEntity.spec, + ...packageEntity.spec, packages: [ - '@backstage/plugin-search', - '@backstage/plugin-search-backend', + '@backstage/package-search', + '@backstage/package-search-backend', ], }, }; - const result = await processor.preProcessEntity(searchPlugin); + const result = await processor.preProcessEntity(searchPackage); expect(result?.spec?.installStatus).toBe(InstallStatus.Installed); }); - it('should not process any other kind other than plugin', async () => { - const processor = new LocalPluginInstallStatusProcessor(); + it('should not process any other kind other than package', async () => { + const processor = new LocalPackageInstallStatusProcessor(); const testEntity = { - ...pluginEntity, + ...packageEntity, kind: 'TestKind', }; const result = await processor.preProcessEntity(testEntity); expect(result).toEqual(testEntity); }); - - it('should return correct values when isJson method is called', async () => { - const processor = new LocalPluginInstallStatusProcessor(); - expect(processor.isJSON(null as any)).toBe(false); - expect(processor.isJSON(undefined as any)).toBe(false); - expect(processor.isJSON(123 as any)).toBe(false); - expect(processor.isJSON('@backstage/plugin-search')).toBe(false); - expect(processor.isJSON('{ "name": "@backstage/plugin-search" }')).toBe( - true, - ); - }); }); diff --git a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPluginInstallStatusProcessor.ts b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPackageInstallStatusProcessor.ts similarity index 70% rename from workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPluginInstallStatusProcessor.ts rename to workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPackageInstallStatusProcessor.ts index 27edcf2df..f245d6cc8 100644 --- a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPluginInstallStatusProcessor.ts +++ b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/LocalPackageInstallStatusProcessor.ts @@ -1,5 +1,5 @@ /* - * Copyright Red Hat, Inc. + * Copyright The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,19 +17,18 @@ import fs from 'fs'; import path from 'path'; import semver from 'semver'; -import { Entity } from '@backstage/catalog-model'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { InstallStatus, MARKETPLACE_API_VERSION, - MarketplaceKinds, - MarketplacePlugin, + MarketplaceKind, + MarketplacePackage, } from '@red-hat-developer-hub/backstage-plugin-marketplace-common'; /** * @public */ -export class LocalPluginInstallStatusProcessor implements CatalogProcessor { +export class LocalPackageInstallStatusProcessor implements CatalogProcessor { private workspacesPath = this.findWorkspacesPath(); private customPaths; @@ -46,7 +45,7 @@ export class LocalPluginInstallStatusProcessor implements CatalogProcessor { } getProcessorName(): string { - return 'LocalPluginInstallStatusProcessor'; + return 'LocalPackageInstallStatusProcessor'; } findWorkspacesPath(startPath = process.cwd()) { @@ -74,7 +73,7 @@ export class LocalPluginInstallStatusProcessor implements CatalogProcessor { packageName: string, packageJsonPath: string, versionRange?: string, - ): Boolean { + ): boolean { try { const absolutePackageJsonPath = path.resolve(packageJsonPath); @@ -125,52 +124,20 @@ export class LocalPluginInstallStatusProcessor implements CatalogProcessor { } } - isJSON(str: string) { - if (typeof str !== 'string') { - return false; - } - - try { - const parsed = JSON.parse(str); - return typeof parsed === 'object' && parsed !== null; - } catch (e) { - return false; - } - } - - async preProcessEntity(entity: MarketplacePlugin): Promise { + async preProcessEntity(entity: MarketplacePackage): Promise { if ( entity.apiVersion === MARKETPLACE_API_VERSION && - entity.kind === MarketplaceKinds.plugin + entity.kind === MarketplaceKind.Package ) { let installStatus: InstallStatus = InstallStatus.NotInstalled; - if (entity?.spec?.packages?.length) { - const somePackagesInstalled = entity.spec.packages.some( - marketplacePackageOrString => { - const npmPackage = - typeof marketplacePackageOrString === 'string' - ? { - name: marketplacePackageOrString, - } - : marketplacePackageOrString; - - const versions = npmPackage?.version?.split(','); - return versions - ? versions?.every(version => - this.customPaths.some(cpath => - this.isPackageInstalled(npmPackage?.name, cpath, version), - ), - ) - : this.customPaths.some(cpath => - this.isPackageInstalled(npmPackage?.name, cpath), - ); - }, - ); + if (entity.spec?.packageName) { + const packageName = entity.spec?.packageName; + // TODO const versions = entity.spec.version; - installStatus = somePackagesInstalled - ? InstallStatus.Installed - : InstallStatus.NotInstalled; + if (this.customPaths.some((cpath) => this.isPackageInstalled(packageName, cpath))) { + installStatus = InstallStatus.Installed; + } } return { diff --git a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/index.ts b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/index.ts index 39ef09cd3..1cd74226c 100644 --- a/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/index.ts +++ b/workspaces/marketplace/plugins/catalog-backend-module-marketplace/src/processors/index.ts @@ -16,6 +16,6 @@ export * from './MarketplacePluginProcessor'; export * from './MarketplacePluginListProcessor'; -export * from './DynamicPluginInstallStatusProcessor'; -export * from './LocalPluginInstallStatusProcessor'; +export * from './DynamicPackageInstallStatusProcessor'; +export * from './LocalPackageInstallStatusProcessor'; export * from './MarketplacePackageProcessor'; diff --git a/workspaces/marketplace/plugins/marketplace-common/src/types.ts b/workspaces/marketplace/plugins/marketplace-common/src/types.ts index 8f5e06194..c4334c9a4 100644 --- a/workspaces/marketplace/plugins/marketplace-common/src/types.ts +++ b/workspaces/marketplace/plugins/marketplace-common/src/types.ts @@ -141,7 +141,6 @@ export type MarketplacePluginPackage = { */ export interface MarketplacePluginSpec extends JsonObject { packages?: (string | MarketplacePluginPackage)[]; - installStatus?: keyof typeof InstallStatus; icon?: string; categories?: string[]; developer?: string; @@ -180,7 +179,7 @@ export type GetPluginsRequest = { * @public */ export interface MarketplacePackageSpec extends JsonObject { - packageName: string; + packageName?: string; dynamicArtifact?: string; author?: string; support?: string; @@ -189,6 +188,7 @@ export interface MarketplacePackageSpec extends JsonObject { appConfigExamples?: AppConfigExample[]; owner?: string; partOf?: string[]; + installStatus?: keyof typeof InstallStatus; } /**