Skip to content

Commit

Permalink
chore(marketplace): move installstatus from plugin to package
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Jerolimov <[email protected]>
  • Loading branch information
christoph-jerolimov committed Feb 11, 2025
1 parent ad6713c commit d1a07d7
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -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),
);
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
);
});

Expand All @@ -94,7 +94,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
new Response(JSON.stringify(pluginsMock), { status: 200 }),
);

const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand All @@ -109,7 +109,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
});

it('should handle non-200 responses gracefully', async () => {
const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand All @@ -129,7 +129,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
};
(cache.get as jest.Mock).mockResolvedValue(cachedData);

const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand All @@ -151,7 +151,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
new Response(JSON.stringify(pluginsMock), { status: 200 }),
);

const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand All @@ -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,
);
Expand Down Expand Up @@ -201,7 +201,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
new Response(JSON.stringify(pluginsMock), { status: 200 }),
);

const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand Down Expand Up @@ -229,7 +229,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
new Response(JSON.stringify(pluginsMock), { status: 200 }),
);

const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand All @@ -252,7 +252,7 @@ describe('DynamicPluginInstallStatusProcessor', () => {
spec: {},
};

const processor = new DynamicPluginInstallStatusProcessor(
const processor = new DynamicPackageInstallStatusProcessor(
discoveryService,
authService,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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',
});
Expand Down Expand Up @@ -118,7 +118,7 @@ export class DynamicPluginInstallStatusProcessor implements CatalogProcessor {
): Promise<MarketplacePlugin> {
if (
entity.apiVersion === MARKETPLACE_API_VERSION &&
entity.kind === MarketplaceKind.Plugin
entity.kind === MarketplaceKind.Package
) {
if (entity.spec?.installStatus === InstallStatus.Installed) {
return entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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,
);
});
});
Loading

0 comments on commit d1a07d7

Please sign in to comment.