From e56619753b936c20340ff40176e5d27af1e67cc9 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 18 Jun 2026 10:12:02 -0400 Subject: [PATCH 1/2] feat(extensions): replace disabled with enabled field in plugin configuration Signed-off-by: Hope Hadfield --- .../cli/src/commands/generate/types.ts | 3 +- .../invalidPluginsConfigBadPackageFormat.yaml | 2 +- .../__fixtures__/data/validPluginsConfig.yaml | 6 +- .../__fixtures__/mockData.ts | 14 +-- .../FileInstallationStorage.test.ts | 48 +++++----- .../installation/FileInstallationStorage.ts | 12 +-- .../InstallationDataService.test.ts | 22 ++--- .../installation/InstallationDataService.ts | 11 +-- .../extensions-backend/src/router.test.ts | 91 +++++++++++++------ .../plugins/extensions-backend/src/router.ts | 30 +++--- .../src/validation/configValidation.test.ts | 18 ++-- .../src/validation/configValidation.ts | 7 ++ .../src/api/ExtensionsApi.ts | 8 +- .../src/api/ExtensionsBackendClient.ts | 12 +-- .../ExtensionsPackageEditContent.tsx | 4 +- .../components/ExtensionsPluginContent.tsx | 2 +- .../ExtensionsPluginInstallContent.test.tsx | 4 +- .../ExtensionsPluginInstallContent.tsx | 2 +- .../InstalledPackages/RowActions.tsx | 2 +- .../extensions/src/hooks/useEnablePlugin.ts | 8 +- .../plugins/extensions/src/utils.test.ts | 20 ++-- .../plugins/extensions/src/utils.ts | 14 +-- 22 files changed, 192 insertions(+), 148 deletions(-) diff --git a/workspaces/extensions/packages/cli/src/commands/generate/types.ts b/workspaces/extensions/packages/cli/src/commands/generate/types.ts index c628b4b703..0b247f29be 100644 --- a/workspaces/extensions/packages/cli/src/commands/generate/types.ts +++ b/workspaces/extensions/packages/cli/src/commands/generate/types.ts @@ -22,7 +22,8 @@ export interface DynamicPluginsConfig { export interface DynamicPluginConfig { package: string; - disabled: boolean; + enabled?: boolean; + disabled?: boolean; pluginConfig: JsonObject; } diff --git a/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/invalidPluginsConfigBadPackageFormat.yaml b/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/invalidPluginsConfigBadPackageFormat.yaml index a0cff978bf..30d044bb89 100644 --- a/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/invalidPluginsConfigBadPackageFormat.yaml +++ b/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/invalidPluginsConfigBadPackageFormat.yaml @@ -1,3 +1,3 @@ plugins: - package: '' - disabled: true + enabled: false diff --git a/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/validPluginsConfig.yaml b/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/validPluginsConfig.yaml index 18a1b6aba2..97ca5bd64f 100644 --- a/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/validPluginsConfig.yaml +++ b/workspaces/extensions/plugins/extensions-backend/__fixtures__/data/validPluginsConfig.yaml @@ -1,8 +1,8 @@ plugins: - package: ./dynamic-plugins/dist/package11-backend-dynamic - disabled: true + enabled: false - package: ./dynamic-plugins/dist/package12 - disabled: true + enabled: false pluginConfig: dynamicPlugins: frontend: @@ -11,4 +11,4 @@ plugins: - path: /package12 importName: Package12Page - package: ./dynamic-plugins/dist/package21-backend-dynamic - disabled: true + enabled: false diff --git a/workspaces/extensions/plugins/extensions-backend/__fixtures__/mockData.ts b/workspaces/extensions/plugins/extensions-backend/__fixtures__/mockData.ts index 02be9e2ffe..4199b1c24b 100644 --- a/workspaces/extensions/plugins/extensions-backend/__fixtures__/mockData.ts +++ b/workspaces/extensions/plugins/extensions-backend/__fixtures__/mockData.ts @@ -127,18 +127,18 @@ export const mockPackages = [ export const mockDynamicPackage11 = { package: mockPackages[0].spec.dynamicArtifact, - disabled: true, + enabled: false, }; export const mockDynamicPackage12 = { package: mockPackages[1].spec.dynamicArtifact, - disabled: true, + enabled: false, pluginConfig: mockPackages[1].spec.appConfigExamples?.at(0)?.content, }; export const mockDynamicPackage21 = { package: mockPackages[2].spec.dynamicArtifact, - disabled: true, + enabled: false, }; export const mockDynamicPlugin1 = [mockDynamicPackage11, mockDynamicPackage12]; @@ -161,8 +161,8 @@ export const mockFileInstallationStorage = { }), updatePackage: jest.fn(), updatePackages: jest.fn(), - setPackageDisabled: jest.fn(), - setPackagesDisabled: jest.fn(), + setPackageEnabled: jest.fn(), + setPackagesEnabled: jest.fn(), } as unknown as jest.Mocked; export const mockInstallationDataService = { @@ -171,8 +171,8 @@ export const mockInstallationDataService = { getInitializationError: jest.fn().mockReturnValue(undefined), updatePackageConfig: jest.fn(), updatePluginConfig: jest.fn(), - setPackageDisabled: jest.fn(), - setPluginDisabled: jest.fn(), + setPackageEnabled: jest.fn(), + setPluginEnabled: jest.fn(), } as unknown as jest.Mocked; export const mockExtensionsApi = { diff --git a/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.test.ts b/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.test.ts index 18acedc075..6a4dba7480 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.test.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.test.ts @@ -158,7 +158,7 @@ describe('FileInstallationStorage', () => { ); const updatedPackage = { ...mockDynamicPackage21, - disabled: false, + enabled: true, }; const fileInstallationStorage = new FileInstallationStorage( configFileName, @@ -186,7 +186,7 @@ describe('FileInstallationStorage', () => { ); const newPackage = { package: newPackageName, - disabled: true, + enabled: false, }; const fileInstallationStorage = new FileInstallationStorage( configFileName, @@ -248,12 +248,12 @@ describe('FileInstallationStorage', () => { ); const addedPackage = { package: './dynamic-plugins/dist/package11-backend-module-dynamic', - disabled: true, + enabled: false, }; const updatedPlugin = [ { ...mockDynamicPackage11, - disabled: false, + enabled: true, pluginConfig: { plugin1: { setting: true, @@ -262,7 +262,7 @@ describe('FileInstallationStorage', () => { }, { ...mockDynamicPackage12, - disabled: false, + enabled: true, pluginConfig: { dynamicPlugins: { frontend: { @@ -310,7 +310,7 @@ describe('FileInstallationStorage', () => { const newPlugin = [ { package: newPackageName, - disabled: true, + enabled: false, }, ]; const fileInstallationStorage = new FileInstallationStorage( @@ -352,7 +352,7 @@ describe('FileInstallationStorage', () => { }); }); - describe('setPackageDisabled', () => { + describe('setPackageEnabled', () => { afterEach(() => { fs.writeFileSync( resolve(__dirname, '../../__fixtures__/data/validPluginsConfig.yaml'), @@ -366,7 +366,7 @@ describe('FileInstallationStorage', () => { ); }); - it('should add new package with disabled status', () => { + it('should add new package with enabled status', () => { const configFileName = resolve( __dirname, '../../__fixtures__/data/validPluginsConfig.yaml', @@ -376,7 +376,7 @@ describe('FileInstallationStorage', () => { ); fileInstallationStorage.initialize(); - fileInstallationStorage.setPackageDisabled(newPackageName, false); + fileInstallationStorage.setPackageEnabled(newPackageName, true); const updatedCatalogInfoYaml = fs.readFileSync(configFileName, 'utf8'); const configYaml = parse(updatedCatalogInfoYaml); @@ -384,11 +384,11 @@ describe('FileInstallationStorage', () => { mockDynamicPackage11, mockDynamicPackage12, mockDynamicPackage21, - { package: newPackageName, disabled: false }, + { package: newPackageName, enabled: true }, ]); }); - it('should update existing package with disabled status', () => { + it('should update existing package with enabled status', () => { const configFileName = resolve( __dirname, '../../__fixtures__/data/validPluginsConfig.yaml', @@ -398,22 +398,22 @@ describe('FileInstallationStorage', () => { ); fileInstallationStorage.initialize(); - fileInstallationStorage.setPackageDisabled( + fileInstallationStorage.setPackageEnabled( mockDynamicPackage12.package, - false, + true, ); const updatedCatalogInfoYaml = fs.readFileSync(configFileName, 'utf8'); const configYaml = parse(updatedCatalogInfoYaml); expect(configYaml.plugins).toEqual([ mockDynamicPackage11, - { ...mockDynamicPackage12, disabled: false }, + { ...mockDynamicPackage12, enabled: true }, mockDynamicPackage21, ]); }); }); - describe('setPackagesDisabled', () => { + describe('setPackagesEnabled', () => { afterEach(() => { fs.writeFileSync( resolve(__dirname, '../../__fixtures__/data/validPluginsConfig.yaml'), @@ -427,7 +427,7 @@ describe('FileInstallationStorage', () => { ); }); - it('should set disabled for existing plugin in the config', () => { + it('should set enabled for existing plugin in the config', () => { const configFileName = resolve( __dirname, '../../__fixtures__/data/validPluginsConfig.yaml', @@ -435,11 +435,11 @@ describe('FileInstallationStorage', () => { const updatedPlugin = [ { ...mockDynamicPackage11, - disabled: false, + enabled: true, }, { ...mockDynamicPackage12, - disabled: false, + enabled: true, }, ]; const fileInstallationStorage = new FileInstallationStorage( @@ -447,9 +447,9 @@ describe('FileInstallationStorage', () => { ); fileInstallationStorage.initialize(); - fileInstallationStorage.setPackagesDisabled( + fileInstallationStorage.setPackagesEnabled( new Set([mockDynamicPackage11.package, mockDynamicPackage12.package]), - false, + true, ); const updatedCatalogInfoYaml = fs.readFileSync(configFileName, 'utf8'); @@ -460,7 +460,7 @@ describe('FileInstallationStorage', () => { ]); }); - it('should set disabled for new plugin in the config', () => { + it('should set enabled for new plugin in the config', () => { const configFileName = resolve( __dirname, '../../__fixtures__/data/validPluginsConfig.yaml', @@ -468,7 +468,7 @@ describe('FileInstallationStorage', () => { const newPlugin = [ { package: newPackageName, - disabled: false, + enabled: true, }, ]; const fileInstallationStorage = new FileInstallationStorage( @@ -476,9 +476,9 @@ describe('FileInstallationStorage', () => { ); fileInstallationStorage.initialize(); - fileInstallationStorage.setPackagesDisabled( + fileInstallationStorage.setPackagesEnabled( new Set([newPackageName]), - false, + true, ); const updatedCatalogInfoYaml = fs.readFileSync(configFileName, 'utf8'); diff --git a/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts b/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts index aa363cfd80..e2be7abbfd 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts @@ -34,8 +34,8 @@ export interface InstallationStorage { updatePackage(packageName: string, newConfig: string): void; getPackages(packageNames: Set): string | undefined; updatePackages(packageNames: Set, newConfig: string): void; - setPackageDisabled(packageName: string, disabled: boolean): void; - setPackagesDisabled(packageNames: Set, disabled: boolean): void; + setPackageEnabled(packageName: string, enabled: boolean): void; + setPackagesEnabled(packageNames: Set, enabled: boolean): void; } export class FileInstallationStorage implements InstallationStorage { @@ -133,18 +133,18 @@ export class FileInstallationStorage implements InstallationStorage { this.save(); } - setPackageDisabled(packageName: string, disabled: boolean) { + setPackageEnabled(packageName: string, enabled: boolean) { let pkg = this.getPackageYamlMap(packageName); if (!pkg) { pkg = new YAMLMap(); pkg.set('package', packageName); this.packages.add(pkg); } - pkg.set('disabled', disabled); + pkg.set('enabled', enabled); this.save(); } - setPackagesDisabled(packageNames: Set, disabled: boolean) { + setPackagesEnabled(packageNames: Set, enabled: boolean) { const packages = this.config.get('plugins') as YAMLSeq< YAMLMap >; @@ -159,7 +159,7 @@ export class FileInstallationStorage implements InstallationStorage { item.set('package', packageName); packages.add(item); } - item.set('disabled', disabled); + item.set('enabled', enabled); } this.save(); diff --git a/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.test.ts b/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.test.ts index bcdd5dc3f6..321b8cadbb 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.test.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.test.ts @@ -200,7 +200,7 @@ describe('InstallationDataService', () => { }); it('should update package', () => { - const newConfig = stringify({ ...mockDynamicPackage11, disabled: false }); + const newConfig = stringify({ ...mockDynamicPackage11, enabled: true }); installationDataService.updatePackageConfig( mockDynamicPackage11.package, newConfig, @@ -237,7 +237,7 @@ describe('InstallationDataService', () => { }); }); - describe('setPackageDisabled', () => { + describe('setPackageEnabled', () => { beforeEach(() => { installationDataService = InstallationDataService.fromConfig({ config: validConfig, @@ -246,19 +246,19 @@ describe('InstallationDataService', () => { }); }); - it('should set package with disabled', async () => { - installationDataService.setPackageDisabled( + it('should set package enabled', async () => { + installationDataService.setPackageEnabled( mockDynamicPackage11.package, false, ); expect( - mockFileInstallationStorage.setPackageDisabled, + mockFileInstallationStorage.setPackageEnabled, ).toHaveBeenCalledWith(mockDynamicPackage11.package, false); }); }); - describe('setPluginDisabled', () => { + describe('setPluginEnabled', () => { beforeEach(() => { installationDataService = InstallationDataService.fromConfig({ config: validConfig, @@ -267,15 +267,13 @@ describe('InstallationDataService', () => { }); }); - it('should set plugin disabled', async () => { - await installationDataService.setPluginDisabled(plugin, true); + it('should set plugin enabled', async () => { + await installationDataService.setPluginEnabled(plugin, true); - expect( - mockFileInstallationStorage.setPackagesDisabled, - ).toHaveBeenCalled(); + expect(mockFileInstallationStorage.setPackagesEnabled).toHaveBeenCalled(); expect( - mockFileInstallationStorage.setPackagesDisabled, + mockFileInstallationStorage.setPackagesEnabled, ).toHaveBeenCalledWith( new Set([ mockPackages[0].spec.dynamicArtifact, diff --git a/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.ts b/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.ts index 9baffe0971..e1c39605b6 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.ts @@ -167,15 +167,12 @@ export class InstallationDataService { this.installationStorage.updatePackages(dynamicArtifacts, newConfig); } - setPackageDisabled(packageDynamicArtifact: string, disabled: boolean) { - this.installationStorage.setPackageDisabled( - packageDynamicArtifact, - disabled, - ); + setPackageEnabled(packageDynamicArtifact: string, enabled: boolean) { + this.installationStorage.setPackageEnabled(packageDynamicArtifact, enabled); } - async setPluginDisabled(plugin: ExtensionsPlugin, disabled: boolean) { + async setPluginEnabled(plugin: ExtensionsPlugin, enabled: boolean) { const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin); - this.installationStorage.setPackagesDisabled(dynamicArtifacts, disabled); + this.installationStorage.setPackagesEnabled(dynamicArtifacts, enabled); } } diff --git a/workspaces/extensions/plugins/extensions-backend/src/router.test.ts b/workspaces/extensions/plugins/extensions-backend/src/router.test.ts index 9006e6b5c9..363e084b10 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/router.test.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/router.test.ts @@ -154,7 +154,7 @@ const configurationEndpointsTestCases = [ req.patch( '/api/extensions/package/default/package11/configuration/disable', ), - body: { disabled: true }, + body: { enabled: false }, }, { description: 'GET /plugin/:namespace/:name/configuration', @@ -172,7 +172,7 @@ const configurationEndpointsTestCases = [ description: 'PATCH /plugin/:namespace/:name/configuration/disable', reqBuilder: (req: request.SuperTest) => req.patch('/api/extensions/plugin/default/plugin1/configuration/disable'), - body: { disabled: true }, + body: { enabled: false }, }, ]; @@ -633,22 +633,22 @@ describe('createRouter', () => { }); describe('PATCH /plugin/:namespace/:name/configuration/disable', () => { - it('should fail when disabled missing with InputError 400', async () => { + it('should fail when neither enabled nor disabled is present with InputError 400', async () => { const { backendServer } = await setupTestWithMockCatalog(PLUGIN_SETUP); const response = await request(backendServer).patch( '/api/extensions/plugin/default/plugin1/configuration/disable', ); - expectInputError(response, "'disabled' must be present boolean"); + expectInputError(response, "'enabled' must be a present boolean"); }); - it('should fail when bad disabled format with InputError 400', async () => { + it('should fail when enabled is not a boolean with InputError 400', async () => { const { backendServer } = await setupTestWithMockCatalog(PLUGIN_SETUP); const response = await request(backendServer) .patch('/api/extensions/plugin/default/plugin1/configuration/disable') - .send({ disabled: 'invalid' }); - expectInputError(response, "'disabled' must be present boolean"); + .send({ enabled: 'invalid' }); + expectInputError(response, "'enabled' must be a present boolean"); }); it('should fail when plugin not found with NotFoundError 404', async () => { @@ -660,24 +660,40 @@ describe('createRouter', () => { const response = await request(backendServer) .patch('/api/extensions/plugin/default/not-found/configuration/disable') - .send({ disabled: true }); + .send({ enabled: false }); expectNotFoundError(response, ExtensionsKind.Plugin); }); it.each([ - ['enable', false], - ['disable', true], - ])('should %s the plugin configuration', async (_, disabled) => { + ['enable', true], + ['disable', false], + ])( + 'should %s the plugin configuration via enabled field', + async (_, enabled) => { + const { backendServer } = await setupTestWithMockCatalog(PLUGIN_SETUP); + + const response = await request(backendServer) + .patch('/api/extensions/plugin/default/plugin1/configuration/disable') + .send({ enabled }); + expect( + mockInstallationDataService.setPluginEnabled, + ).toHaveBeenCalledWith(mockPlugins[0], enabled); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'OK' }); + }, + ); + + it('should accept legacy disabled field for backward compatibility', async () => { const { backendServer } = await setupTestWithMockCatalog(PLUGIN_SETUP); const response = await request(backendServer) .patch('/api/extensions/plugin/default/plugin1/configuration/disable') - .send({ disabled }); - expect( - mockInstallationDataService.setPluginDisabled, - ).toHaveBeenCalledWith(mockPlugins[0], disabled); + .send({ disabled: true }); + expect(mockInstallationDataService.setPluginEnabled).toHaveBeenCalledWith( + mockPlugins[0], + false, + ); expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'OK' }); }); }); @@ -774,24 +790,24 @@ describe('createRouter', () => { }); describe('PATCH /package/:namespace/:name/configuration/disable', () => { - it('should fail when disabled missing with InputError 400', async () => { + it('should fail when neither enabled nor disabled is present with InputError 400', async () => { const { backendServer } = await setupTestWithMockCatalog(PACKAGE_SETUP); const response = await request(backendServer).patch( '/api/extensions/package/default/package11/configuration/disable', ); - expectInputError(response, "'disabled' must be present boolean"); + expectInputError(response, "'enabled' must be a present boolean"); }); - it('should fail when bad disabled format with InputError 400', async () => { + it('should fail when enabled is not a boolean with InputError 400', async () => { const { backendServer } = await setupTestWithMockCatalog(PACKAGE_SETUP); const response = await request(backendServer) .patch( '/api/extensions/package/default/package11/configuration/disable', ) - .send({ disabled: 'invalid' }); - expectInputError(response, "'disabled' must be present boolean"); + .send({ enabled: 'invalid' }); + expectInputError(response, "'enabled' must be a present boolean"); }); it('should fail when package not found with NotFoundError 404', async () => { @@ -806,26 +822,43 @@ describe('createRouter', () => { .patch( '/api/extensions/package/default/not-found/configuration/disable', ) - .send({ disabled: true }); + .send({ enabled: false }); expectNotFoundError(response, ExtensionsKind.Package); }); it.each([ - ['enable', false], - ['disable', true], - ])('should %s the package configuration', async (_, disabled) => { + ['enable', true], + ['disable', false], + ])( + 'should %s the package configuration via enabled field', + async (_, enabled) => { + const { backendServer } = await setupTestWithMockCatalog(PACKAGE_SETUP); + + const response = await request(backendServer) + .patch( + '/api/extensions/package/default/package11/configuration/disable', + ) + .send({ enabled }); + expect( + mockInstallationDataService.setPackageEnabled, + ).toHaveBeenCalledWith(mockDynamicPackage11.package, enabled); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'OK' }); + }, + ); + + it('should accept legacy disabled field for backward compatibility', async () => { const { backendServer } = await setupTestWithMockCatalog(PACKAGE_SETUP); const response = await request(backendServer) .patch( '/api/extensions/package/default/package11/configuration/disable', ) - .send({ disabled }); + .send({ disabled: true }); expect( - mockInstallationDataService.setPackageDisabled, - ).toHaveBeenCalledWith(mockDynamicPackage11.package, disabled); + mockInstallationDataService.setPackageEnabled, + ).toHaveBeenCalledWith(mockDynamicPackage11.package, false); expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'OK' }); }); }); diff --git a/workspaces/extensions/plugins/extensions-backend/src/router.ts b/workspaces/extensions/plugins/extensions-backend/src/router.ts index 8c60c62aee..d6043a4e1a 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/router.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/router.ts @@ -55,6 +55,20 @@ import { DynamicPluginProvider, } from '@backstage/backend-dynamic-feature-service'; +/** + * Resolve the effective `enabled` value from a request body that may contain + * `enabled`, `disabled`, or both. When both are present `enabled` wins. + * Throws InputError when neither field is a boolean. + */ +function resolveEnabledField(body: Record): boolean { + const hasEnabled = typeof body.enabled === 'boolean'; + const hasDisabled = typeof body.disabled === 'boolean'; + + if (hasEnabled) return body.enabled as boolean; + if (hasDisabled) return !(body.disabled as boolean); + throw new InputError("'enabled' must be a present boolean"); +} + export type ExtensionsRouterOptions = { httpAuth: HttpAuthService; extensionsApi: ExtensionsApi; @@ -315,13 +329,10 @@ export async function createRouter( ); } - const disabled = req.body.disabled; - if (typeof disabled !== 'boolean') { - throw new InputError("'disabled' must be present boolean"); - } - installationDataService.setPackageDisabled( + const enabled = resolveEnabledField(req.body); + installationDataService.setPackageEnabled( extensionsPackage.spec.dynamicArtifact, - disabled, + enabled, ); res.status(200).json({ status: 'OK' }); }, @@ -451,11 +462,8 @@ export async function createRouter( req, extensionsPluginWritePermission, ); - const disabled = req.body.disabled; - if (typeof disabled !== 'boolean') { - throw new InputError("'disabled' must be present boolean"); - } - await installationDataService.setPluginDisabled(plugin, disabled); + const enabled = resolveEnabledField(req.body); + await installationDataService.setPluginEnabled(plugin, enabled); res.status(200).json({ status: 'OK' }); }, ); diff --git a/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.test.ts b/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.test.ts index 33fd0033df..9cf3ad8278 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.test.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.test.ts @@ -31,7 +31,7 @@ describe('validateConfigurationFormat', () => { plugins: - package: package1 - package: package2 - disabled: false + enabled: true integrity: dummyabcd pluginConfig: key: value @@ -58,7 +58,7 @@ describe('validatePackageFormat', () => { testCase: 'correct package', yaml: ` package: package1 - disabled: true + enabled: false pluginConfig: key: value key2: @@ -67,7 +67,7 @@ describe('validatePackageFormat', () => { `, }, { - testCase: "missing 'disabled' and 'pluginConfig'", + testCase: "missing 'enabled' and 'pluginConfig'", yaml: ` package: package1 `, @@ -86,7 +86,7 @@ describe('validatePackageFormat', () => { { testCase: "'package' is missing", yaml: ` - disabled: false + enabled: true `, error: "'package' field in package item must be a non-empty string", }, @@ -98,12 +98,12 @@ describe('validatePackageFormat', () => { error: "'package' field in package item must be a non-empty string", }, { - testCase: "'disabled' is not a boolean", + testCase: "'enabled' is not a boolean", yaml: ` package: package1 - disabled: "not a boolean" + enabled: "not a boolean" `, - error: "optional 'disabled' field in package item must be a boolean", + error: "optional 'enabled' field in package item must be a boolean", }, { testCase: "'integrity' is not a string", @@ -126,7 +126,7 @@ describe('validatePackageFormat', () => { testCase: "'packageName' differs", yaml: ` package: package1 - disabled: false + enabled: true `, error: "'package' field value in package item differs from 'different-package'", @@ -152,7 +152,7 @@ describe('validatePluginFormat', () => { const doc = parseDocument(` - package: package1 - package: package2 - disabled: false + enabled: true pluginConfig: key: value `); diff --git a/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts b/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts index 7663680bc9..bfe209c0ab 100644 --- a/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts +++ b/workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts @@ -59,6 +59,13 @@ export function validatePackageFormat( ); } + const enabled = item.get('enabled'); + if (enabled && typeof enabled !== 'boolean') { + throw new ConfigFormatError( + "Invalid installation configuration, optional 'enabled' field in package item must be a boolean", + ); + } + const disabled = item.get('disabled'); if (disabled && typeof disabled !== 'boolean') { throw new ConfigFormatError( diff --git a/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsApi.ts b/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsApi.ts index 5ef5ddd06c..188b581965 100644 --- a/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsApi.ts +++ b/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsApi.ts @@ -88,10 +88,10 @@ export interface ExtensionsApi { configYaml: string, ): Promise<{ status: string }>; - disablePackage?( + enablePackage?( namespace: string, name: string, - disabled: boolean, + enabled: boolean, ): Promise<{ status: string }>; getPlugins( @@ -124,10 +124,10 @@ export interface ExtensionsApi { configYaml: string, ): Promise<{ status: string }>; - disablePlugin?( + enablePlugin?( namespace: string, name: string, - disabled: boolean, + enabled: boolean, ): Promise<{ status: string }>; getPluginPackages( diff --git a/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsBackendClient.ts b/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsBackendClient.ts index 913eac0f19..4ae1d4b9a2 100644 --- a/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsBackendClient.ts +++ b/workspaces/extensions/plugins/extensions-common/src/api/ExtensionsBackendClient.ts @@ -201,16 +201,16 @@ export class ExtensionsBackendClient implements ExtensionsApi { ); } - async disablePackage( + async enablePackage( namespace: string, name: string, - disabled: boolean, + enabled: boolean, ): Promise<{ status: string }> { return this.request( `/package/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/configuration/disable`, 'PATCH', undefined, - { disabled }, + { enabled }, ); } @@ -281,16 +281,16 @@ export class ExtensionsBackendClient implements ExtensionsApi { ); } - async disablePlugin( + async enablePlugin( namespace: string, name: string, - disabled: boolean, + enabled: boolean, ): Promise<{ status: string }> { return this.request( `/plugin/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/configuration/disable`, 'PATCH', undefined, - { disabled }, + { enabled }, ); } diff --git a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx index 9b18712f15..79cacf3e80 100644 --- a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx +++ b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx @@ -111,7 +111,7 @@ export const ExtensionsPackageEditContent = ({ } if (pkgConfig.isSuccess && !existing) { const path = pkg.spec?.dynamicArtifact ?? './dynamic-plugins/dist/....'; - const yamlContent = `plugins:\n - package: ${JSON.stringify(path)}\n disabled: false\n`; + const yamlContent = `plugins:\n - package: ${JSON.stringify(path)}\n enabled: true\n`; codeEditor.setValue(yamlContent); } }, [ @@ -142,7 +142,7 @@ export const ExtensionsPackageEditContent = ({ const current = codeEditor.getValue(); if (!current || current.trim() === '') { const path = pkg.spec?.dynamicArtifact ?? './dynamic-plugins/dist/....'; - const yamlContent = `plugins:\n - package: ${JSON.stringify(path)}\n disabled: false\n`; + const yamlContent = `plugins:\n - package: ${JSON.stringify(path)}\n enabled: true\n`; codeEditor.setValue(yamlContent); } return; diff --git a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginContent.tsx b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginContent.tsx index 73938af231..1f7da9b278 100644 --- a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginContent.tsx +++ b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginContent.tsx @@ -346,7 +346,7 @@ export const ExtensionsPluginContent = ({ const res = await enablePlugin({ namespace: plugin.metadata.namespace ?? 'default', name: plugin.metadata.name, - disabled: !newValue, + enabled: newValue, }); if (res?.status !== 'OK') { diff --git a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.test.tsx b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.test.tsx index 9de08662b6..1be4959f98 100644 --- a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.test.tsx +++ b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.test.tsx @@ -132,7 +132,7 @@ describe('ExtensionsPluginInstallContent', () => { }; const configYaml = - '- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-extensions\n disabled: false # DEBSMITA\n pluginConfig:\n dynamicPlugins:\n frontend:\n red-hat-developer-hub.backstage-plugin-extensions:\n appIcons:\n - name: extensions\n importName: ExtensionsIcon\n dynamicRoutes:\n - path: /extensions/catalog\n importName: DynamicExtensionsPluginRouter\n mountPoints:\n - mountPoint: internal.plugins/tab\n importName: DynamicExtensionsPluginContent\n config:\n path: extensions\n title: Catalog\n- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-extensions-backend-dynamic\n disabled: false\n'; + '- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-extensions\n enabled: true # DEBSMITA\n pluginConfig:\n dynamicPlugins:\n frontend:\n red-hat-developer-hub.backstage-plugin-extensions:\n appIcons:\n - name: extensions\n importName: ExtensionsIcon\n dynamicRoutes:\n - path: /extensions/catalog\n importName: DynamicExtensionsPluginRouter\n mountPoints:\n - mountPoint: internal.plugins/tab\n importName: DynamicExtensionsPluginContent\n config:\n path: extensions\n title: Catalog\n- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-extensions-backend-dynamic\n enabled: true\n'; const packages = [ { @@ -194,7 +194,7 @@ describe('ExtensionsPluginInstallContent', () => { expect(yamlString).toContain( 'package: ./dynamic-plugins/dist/backstage-community-plugin-3scale-backend-dynamic', ); - expect(yamlString).toContain('disabled: false'); + expect(yamlString).toContain('enabled: true'); expect(getByTestId('install')).toBeInTheDocument(); }); diff --git a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.tsx b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.tsx index 333b484b15..f083398fcd 100644 --- a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.tsx +++ b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.tsx @@ -145,7 +145,7 @@ export const ExtensionsPluginInstallContent = ({ plugins: (packages ?? []).map(pkg => { const pkgEntry: ExtensionsPackageSpec = { package: pkg.spec?.dynamicArtifact ?? './dynamic-plugins/dist/....', - disabled: false, + enabled: true, }; if (pkg.spec?.integrity) { pkgEntry.integrity = pkg.spec.integrity; diff --git a/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx b/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx index 10a11f5302..7322facd43 100644 --- a/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx +++ b/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx @@ -344,7 +344,7 @@ export const TogglePackage = ({ const res = await enablePlugin({ namespace: pkg.namespace ?? 'default', name: pkg.name!, - disabled: !newValue, + enabled: newValue, }); if (res?.status === 'OK') { diff --git a/workspaces/extensions/plugins/extensions/src/hooks/useEnablePlugin.ts b/workspaces/extensions/plugins/extensions/src/hooks/useEnablePlugin.ts index 8b88ae492a..c0b17f1e34 100644 --- a/workspaces/extensions/plugins/extensions/src/hooks/useEnablePlugin.ts +++ b/workspaces/extensions/plugins/extensions/src/hooks/useEnablePlugin.ts @@ -21,16 +21,16 @@ import { useExtensionsApi } from './useExtensionsApi'; type EnablePluginVariables = { namespace: string; name: string; - disabled: boolean; + enabled: boolean; }; export const useEnablePlugin = (isPackage: boolean) => { const extensionsApi = useExtensionsApi(); return useMutation({ - mutationFn: async ({ namespace, name, disabled }: EnablePluginVariables) => + mutationFn: async ({ namespace, name, enabled }: EnablePluginVariables) => isPackage - ? await extensionsApi.disablePackage?.(namespace, name, disabled) - : await extensionsApi.disablePlugin?.(namespace, name, disabled), + ? await extensionsApi.enablePackage?.(namespace, name, enabled) + : await extensionsApi.enablePlugin?.(namespace, name, enabled), }); }; diff --git a/workspaces/extensions/plugins/extensions/src/utils.test.ts b/workspaces/extensions/plugins/extensions/src/utils.test.ts index aa27ebc31c..1b077a6100 100644 --- a/workspaces/extensions/plugins/extensions/src/utils.test.ts +++ b/workspaces/extensions/plugins/extensions/src/utils.test.ts @@ -53,7 +53,7 @@ describe('extensions utils', () => { const content = applyContent( `plugins: - package: ./dynamic-plugins/dist/backstage-community-plugin-quay - disabled: false + enabled: true `, 'backstage-community-plugin-quay', packages, @@ -62,7 +62,7 @@ describe('extensions utils', () => { expect(content).toEqual( `plugins: - package: ./dynamic-plugins/dist/backstage-community-plugin-quay - disabled: false + enabled: true ${expectedPluginConfig} `, ); @@ -74,7 +74,7 @@ describe('extensions utils', () => { plugins: - package: ./dynamic-plugins/dist/backstage-community-plugin-quay # some more comment - disabled: false + enabled: true `, 'backstage-community-plugin-quay', packages, @@ -85,7 +85,7 @@ describe('extensions utils', () => { plugins: - package: ./dynamic-plugins/dist/backstage-community-plugin-quay # some more comment - disabled: false + enabled: true ${expectedPluginConfig} `, ); @@ -95,9 +95,9 @@ plugins: const content = applyContent( `plugins: - package: ./dynamic-plugins/dist/backstage-community-plugin-sonarcloud - disabled: false + enabled: true - package: ./dynamic-plugins/dist/backstage-community-plugin-quay - disabled: false + enabled: true `, 'backstage-community-plugin-quay', packages, @@ -106,9 +106,9 @@ plugins: expect(content).toEqual( `plugins: - package: ./dynamic-plugins/dist/backstage-community-plugin-sonarcloud - disabled: false + enabled: true - package: ./dynamic-plugins/dist/backstage-community-plugin-quay - disabled: false + enabled: true ${expectedPluginConfig} `, ); @@ -125,7 +125,7 @@ plugins: expect(content).toContain( 'package: ./dynamic-plugins/dist/backstage-community-plugin-quay', ); - expect(content).toContain('disabled: false'); + expect(content).toContain('enabled: true'); expect(content).toContain('pluginConfig:'); expect(content).toContain('catalog:'); }); @@ -141,7 +141,7 @@ plugins: expect(content).toContain( 'package: ./dynamic-plugins/dist/backstage-community-plugin-quay', ); - expect(content).toContain('disabled: false'); + expect(content).toContain('enabled: true'); expect(content).toContain('pluginConfig:'); }); }); diff --git a/workspaces/extensions/plugins/extensions/src/utils.ts b/workspaces/extensions/plugins/extensions/src/utils.ts index 4617642c1d..17837fbe9f 100644 --- a/workspaces/extensions/plugins/extensions/src/utils.ts +++ b/workspaces/extensions/plugins/extensions/src/utils.ts @@ -41,9 +41,9 @@ export enum ExtensionsStatus { export const DYNAMIC_PLUGIN_CONFIG_YAML = `plugins: - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import-backend-dynamic - disabled: false + enabled: true - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import - disabled: true`; + enabled: false`; export const EXTENSIONS_CONFIG_YAML = `extensions: installation: @@ -99,7 +99,7 @@ export const applyContent = ( plugins: [ { package: packagePath, - disabled: false, + enabled: true, pluginConfig: typeof newContent === 'string' ? parse(newContent) : newContent, }, @@ -139,11 +139,11 @@ export const applyContent = ( if (packagePath) { const newPlugin: { package: string; - disabled: boolean; + enabled: boolean; pluginConfig?: Document | JsonObject; } = { package: packagePath, - disabled: false, + enabled: true, }; if (typeof newContent === 'string') { newPlugin.pluginConfig = parseDocument(newContent); @@ -158,11 +158,11 @@ export const applyContent = ( if (packagePath) { const newPlugin: { package: string; - disabled: boolean; + enabled: boolean; pluginConfig?: Document | JsonObject; } = { package: packagePath, - disabled: false, + enabled: true, }; if (typeof newContent === 'string') { newPlugin.pluginConfig = parseDocument(newContent); From d07fb739473ec297e6eaf94228c4c5faa45a390a Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Mon, 22 Jun 2026 15:37:21 -0400 Subject: [PATCH 2/2] chore: add changeset Signed-off-by: Hope Hadfield --- workspaces/extensions/.changeset/twelve-geese-buy.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 workspaces/extensions/.changeset/twelve-geese-buy.md diff --git a/workspaces/extensions/.changeset/twelve-geese-buy.md b/workspaces/extensions/.changeset/twelve-geese-buy.md new file mode 100644 index 0000000000..9d12886aa2 --- /dev/null +++ b/workspaces/extensions/.changeset/twelve-geese-buy.md @@ -0,0 +1,8 @@ +--- +'@red-hat-developer-hub/backstage-plugin-extensions-backend': minor +'@red-hat-developer-hub/backstage-plugin-extensions-common': minor +'@red-hat-developer-hub/backstage-plugin-extensions': minor +'@red-hat-developer-hub/extensions-cli': minor +--- + +Replaced `disabled` field with `enabled` in plugin configuration