Skip to content

feat(extensions): replace disabled with enabled field in plugin configuration#3536

Open
hopehadfield wants to merge 2 commits into
redhat-developer:mainfrom
hopehadfield:enabled-ui
Open

feat(extensions): replace disabled with enabled field in plugin configuration#3536
hopehadfield wants to merge 2 commits into
redhat-developer:mainfrom
hopehadfield:enabled-ui

Conversation

@hopehadfield

Copy link
Copy Markdown
Member

Hey, I just made a Pull Request!

Summary

Replaces the disabled field with enabled in the extensions plugin UI and backend, per RHIDP-14727 and RHIDP-11983. The backend API accepts both fields for backward compatibility (enabled takes precedence), but the UI and storage layer exclusively use enabled.

Changes

extensions-common — Added enablePackage/enablePlugin to the API interface and backend client. Removed disablePackage/disablePlugin.

extensions-backend — Router accepts both {enabled} and {disabled} in request body via resolveEnabledField(), with enabled taking precedence. Storage writes enabled to YAML. Validation accepts both fields.

extensions (frontend)useEnablePlugin hook and all components send {enabled} exclusively. YAML generation templates use enabled: true/false.

Backward compatibility

  • Existing YAML files with disabled: true/false still pass validation
  • API clients sending {disabled: true} still work (resolved to enabled: false by the router)
  • New configs written by the UI use enabled exclusively

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

…guration

Signed-off-by: Hope Hadfield <hhadfiel@redhat.com>
Signed-off-by: Hope Hadfield <hhadfiel@redhat.com>
@github-actions

Copy link
Copy Markdown
Contributor

This pull request adds a new top-level directory under workspaces/. Please follow Submitting a Pull Request for a New Workspace in CONTRIBUTING.md.

@rhdh-gh-app

rhdh-gh-app Bot commented Jun 22, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/extensions-cli workspaces/extensions/packages/cli minor v0.18.0
@red-hat-developer-hub/backstage-plugin-extensions-backend workspaces/extensions/plugins/extensions-backend minor v0.18.0
@red-hat-developer-hub/backstage-plugin-extensions-common workspaces/extensions/plugins/extensions-common minor v0.18.0
@red-hat-developer-hub/backstage-plugin-extensions workspaces/extensions/plugins/extensions minor v0.18.0

@sonarqubecloud

Copy link
Copy Markdown

@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

feat(extensions): adopt enabled flag for plugin config (legacy disabled supported)
✨ Enhancement ⚙️ Configuration changes 🧪 Tests 🕐 40+ Minutes

Grey Divider

Description

• Replace disabled with enabled across extensions UI, backend API, and storage.
• Keep backward compatibility by accepting legacy disabled requests and YAML inputs.
• Update CLI types, fixtures, and tests to reflect enabled as the canonical field.
Diagram

graph TD
  UI["Extensions UI"] --> Client["ExtensionsBackendClient"] --> Router["extensions-backend router"] --> Service["InstallationDataService"] --> Storage["FileInstallationStorage"] --> YAML[("plugins config YAML")]
  Storage --> Validator["configValidation"]
  CLI["extensions-cli"] --> YAML
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Versioned toggle endpoint (/enable) and deprecate /disable
  • ➕ Clearer API semantics (no inverted boolean via legacy disabled).
  • ➕ Easier to reason about request bodies and future schema evolution.
  • ➖ Requires API consumers to migrate endpoints.
  • ➖ Adds routing/maintenance overhead during deprecation window.
2. Persist both fields during transition (write enabled + disabled)
  • ➕ Older readers that only understand disabled keep working without router translation.
  • ➖ Creates conflicting sources of truth in YAML.
  • ➖ Harder validation rules; must define precedence everywhere.
3. One-time migration step converting stored YAML from disabled→enabled
  • ➕ Eliminates long-term dual-field support in storage.
  • ➕ Reduces ambiguity for operators editing YAML manually.
  • ➖ Migration complexity/error handling (partial writes, backups).
  • ➖ Still need API compatibility for older clients for some time.

Recommendation: The PR’s approach (accept both on the API/validation layer, but write/generate only enabled) is the best trade-off: it keeps backward compatibility for existing YAML and clients, while ensuring new writes converge on a single canonical field. The resolveEnabledField() precedence rule makes the transition explicit and minimizes long-term drift.

Files changed (23) +200 / -148

Enhancement (8) +40 / -24
types.tsAllow 'enabled' in generated dynamic plugin config type +2/-1

Allow 'enabled' in generated dynamic plugin config type

• Updates 'DynamicPluginConfig' to support optional 'enabled' while retaining optional 'disabled' for compatibility. Enables CLI tooling to work with both legacy and new config formats.

workspaces/extensions/packages/cli/src/commands/generate/types.ts

router.tsResolve enabled/disabled request bodies with precedence and toggle via enabled +19/-11

Resolve enabled/disabled request bodies with precedence and toggle via enabled

• Adds 'resolveEnabledField()' to accept 'enabled', 'disabled', or both (with 'enabled' taking precedence). Updates package and plugin toggle routes to call enabled-based InstallationDataService methods.

workspaces/extensions/plugins/extensions-backend/src/router.ts

configValidation.tsAccept optional boolean 'enabled' during YAML validation +7/-0

Accept optional boolean 'enabled' during YAML validation

• Extends 'validatePackageFormat' to allow an optional 'enabled' field and validate it as a boolean. Retains existing 'disabled' validation for backward compatibility.

workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts

ExtensionsPackageEditContent.tsxGenerate initial package YAML with 'enabled: true' +2/-2

Generate initial package YAML with 'enabled: true'

• Updates default YAML template inserted into the editor for new packages. Newly generated config now uses 'enabled: true' as the canonical field.

workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx

ExtensionsPluginContent.tsxToggle plugins by sending 'enabled' state +1/-1

Toggle plugins by sending 'enabled' state

• Updates the enable/disable toggle handler to send '{ enabled: newValue }' to the mutation hook. Removes inverted disabled mapping from the UI path.

workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginContent.tsx

ExtensionsPluginInstallContent.tsxGenerate install YAML entries with 'enabled: true' +1/-1

Generate install YAML entries with 'enabled: true'

• Updates package entries created during install YAML generation to include 'enabled: true' rather than 'disabled: false'. Aligns UI-generated configs with backend storage format.

workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.tsx

RowActions.tsxToggle installed packages by sending 'enabled' state +1/-1

Toggle installed packages by sending 'enabled' state

• Updates row action toggle requests to pass '{ enabled: newValue }' instead of inverting into 'disabled'. Keeps UI behavior consistent across package and plugin toggles.

workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx

utils.tsMake YAML templates and applyContent write 'enabled' +7/-7

Make YAML templates and applyContent write 'enabled'

• Updates default YAML templates and 'applyContent' generation logic to emit 'enabled: true' for new plugin entries. Replaces internal object shapes from 'disabled' to 'enabled' where new entries are created.

workspaces/extensions/plugins/extensions/src/utils.ts

Refactor (5) +24 / -27
FileInstallationStorage.tsPersist 'enabled' field via setPackageEnabled/setPackagesEnabled +6/-6

Persist 'enabled' field via setPackageEnabled/setPackagesEnabled

• Replaces disabled-based toggling methods with enabled-based ones. Storage now writes an 'enabled' boolean into the YAML map/sequence when updating package entries.

workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts

InstallationDataService.tsRename public toggles to enabled-based APIs +4/-7

Rename public toggles to enabled-based APIs

• Renames 'setPackageDisabled'→'setPackageEnabled' and 'setPluginDisabled'→'setPluginEnabled'. Delegates to the corresponding enabled-based storage methods.

workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.ts

ExtensionsApi.tsRename API interface methods to enablePackage/enablePlugin +4/-4

Rename API interface methods to enablePackage/enablePlugin

• Replaces the optional 'disablePackage/disablePlugin' methods with 'enablePackage/enablePlugin' and switches the parameter name to 'enabled'. Updates the shared contract used by frontend and backend client.

workspaces/extensions/plugins/extensions-common/src/api/ExtensionsApi.ts

ExtensionsBackendClient.tsSend '{ enabled }' in PATCH requests via new enable* methods +6/-6

Send '{ enabled }' in PATCH requests via new enable* methods

• Renames backend client methods to 'enablePackage'/'enablePlugin' while keeping the same endpoint path. Updates request bodies to send '{ enabled }' instead of '{ disabled }'.

workspaces/extensions/plugins/extensions-common/src/api/ExtensionsBackendClient.ts

useEnablePlugin.tsRename mutation variable to 'enabled' and call enable* API methods +4/-4

Rename mutation variable to 'enabled' and call enable* API methods

• Updates the hook variable contract from 'disabled' to 'enabled'. Switches to 'extensionsApi.enablePackage/enablePlugin' methods introduced in the common API.

workspaces/extensions/plugins/extensions/src/hooks/useEnablePlugin.ts

Tests (9) +128 / -97
invalidPluginsConfigBadPackageFormat.yamlUpdate invalid config fixture to use 'enabled' +1/-1

Update invalid config fixture to use 'enabled'

• Switches fixture content from 'disabled: true' to 'enabled: false' while keeping the invalid package format scenario intact.

workspaces/extensions/plugins/extensions-backend/fixtures/data/invalidPluginsConfigBadPackageFormat.yaml

validPluginsConfig.yamlUpdate valid config fixture to store 'enabled' flags +3/-3

Update valid config fixture to store 'enabled' flags

• Rewrites plugin entries in the valid fixture to use 'enabled: false' instead of 'disabled: true'. Aligns fixture with the new canonical persisted field.

workspaces/extensions/plugins/extensions-backend/fixtures/data/validPluginsConfig.yaml

mockData.tsRename mocks and storage/service methods to enabled semantics +7/-7

Rename mocks and storage/service methods to enabled semantics

• Updates mock plugin/package configs to use 'enabled' instead of 'disabled'. Renames mocked storage/service methods to 'setPackageEnabled' / 'setPackagesEnabled' / 'setPluginEnabled'.

workspaces/extensions/plugins/extensions-backend/fixtures/mockData.ts

FileInstallationStorage.test.tsUpdate storage tests to assert 'enabled' is written to YAML +24/-24

Update storage tests to assert 'enabled' is written to YAML

• Renames test suites and expectations from disabled toggles to enabled toggles. Ensures new/updated entries persist 'enabled: true/false' in the YAML config.

workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.test.ts

InstallationDataService.test.tsUpdate data service tests for setPackageEnabled/setPluginEnabled +10/-12

Update data service tests for setPackageEnabled/setPluginEnabled

• Adjusts unit tests to call and verify enabled-based methods on the installation storage. Ensures plugin enable flows use 'setPackagesEnabled' under the hood.

workspaces/extensions/plugins/extensions-backend/src/installation/InstallationDataService.test.ts

router.test.tsUpdate router tests to send 'enabled' and cover legacy 'disabled' +62/-29

Update router tests to send 'enabled' and cover legacy 'disabled'

• Switches endpoint tests to send '{ enabled: true/false }' and updates error messages accordingly. Adds explicit backward-compatibility assertions for '{ disabled: true }' mapping to 'enabled: false'.

workspaces/extensions/plugins/extensions-backend/src/router.test.ts

configValidation.test.tsUpdate validation tests to include optional 'enabled' boolean +9/-9

Update validation tests to include optional 'enabled' boolean

• Adjusts YAML snippets and test names to reflect 'enabled' instead of 'disabled' in primary examples. Keeps coverage for validation expectations while aligning with the new canonical field.

workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.test.ts

ExtensionsPluginInstallContent.test.tsxUpdate install content YAML expectations to 'enabled: true' +2/-2

Update install content YAML expectations to 'enabled: true'

• Switches the test YAML input and assertions from 'disabled: false' to 'enabled: true'. Ensures generated install YAML reflects the new canonical field.

workspaces/extensions/plugins/extensions/src/components/ExtensionsPluginInstallContent.test.tsx

utils.test.tsUpdate YAML manipulation utility tests to 'enabled' field +10/-10

Update YAML manipulation utility tests to 'enabled' field

• Rewrites YAML fixtures and assertions to expect 'enabled: true' instead of 'disabled: false'. Confirms 'applyContent' produces canonical 'enabled' entries.

workspaces/extensions/plugins/extensions/src/utils.test.ts

Documentation (1) +8 / -0
twelve-geese-buy.mdAdd changeset for 'disabled'→'enabled' configuration migration +8/-0

Add changeset for 'disabled'→'enabled' configuration migration

• Introduces a changeset marking minor bumps across extensions packages. Documents the switch from 'disabled' to 'enabled' in plugin configuration.

workspaces/extensions/.changeset/twelve-geese-buy.md

@rhdh-qodo-merge

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Tickets: RHIDP-14727

Grey Divider


Remediation recommended

1. Invalid enabled silently ignored 🐞 Bug ≡ Correctness
Description
resolveEnabledField treats enabled as "absent" unless it is a boolean, so a request containing
an invalid enabled plus a valid legacy disabled will be accepted and interpreted via disabled.
This makes mixed/invalid client payloads succeed unexpectedly rather than failing fast.
Code

workspaces/extensions/plugins/extensions-backend/src/router.ts[R63-69]

+function resolveEnabledField(body: Record<string, unknown>): 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");
Relevance

⭐⭐ Medium

No historical evidence on mixed enabled/disabled payload handling; only single-field boolean
validation seen.

PR-#1556
PR-#823

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The resolver only considers enabled when it is a boolean; otherwise it will use disabled if that
is boolean, allowing mixed payloads with invalid enabled to pass.

workspaces/extensions/plugins/extensions-backend/src/router.ts[58-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`resolveEnabledField` falls back to `disabled` when `enabled` is present but not boolean. If clients send both fields, and `enabled` is invalid, the request should fail (or at least not silently fall back), otherwise client bugs get masked.

### Issue Context
The PR states `enabled` takes precedence when both are present; precedence becomes ambiguous when `enabled` is present but invalid.

### Fix Focus Areas
- workspaces/extensions/plugins/extensions-backend/src/router.ts[63-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Invalid enabled passes validation 🐞 Bug ≡ Correctness
Description
validatePackageFormat uses a truthiness check for enabled, so falsy non-boolean values (e.g.,
null or empty string) bypass validation and are accepted as valid config. This can allow malformed
YAML to be persisted and later misinterpreted by readers that expect a strict boolean.
Code

workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts[R62-67]

+  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",
+    );
+  }
Relevance

⭐ Low

Validation historically uses truthiness guards for optional fields (disabled/integrity), so falsy
non-boolean edge cases tolerated.

PR-#823
PR-#1191

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new validation checks enabled using a truthiness guard, meaning the type check is not executed
when enabled is present but falsy and non-boolean.

workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts[62-74]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`validatePackageFormat` checks `enabled` with `if (enabled && typeof enabled !== 'boolean')`, which skips validation for falsy non-boolean values (e.g., `null`, `''`, `0`).

### Issue Context
This PR introduces `enabled` support; validation should enforce that if the field is present, it must be a boolean.

### Fix Focus Areas
- workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts[62-74]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Legacy disabled not cleared 🐞 Bug ≡ Correctness
Description
FileInstallationStorage.setPackageEnabled/setPackagesEnabled set enabled but never remove an
existing legacy disabled field, so toggling an older config can produce YAML entries containing
both fields with potentially conflicting values. This contradicts the PR intent that the storage
layer writes enabled exclusively and makes the resulting config ambiguous.
Code

workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts[R136-150]

+  setPackageEnabled(packageName: string, enabled: boolean) {
    let pkg = this.getPackageYamlMap(packageName);
    if (!pkg) {
      pkg = new YAMLMap<string, JsonValue>();
      pkg.set('package', packageName);
      this.packages.add(pkg);
    }
-    pkg.set('disabled', disabled);
+    pkg.set('enabled', enabled);
    this.save();
  }

-  setPackagesDisabled(packageNames: Set<string>, disabled: boolean) {
+  setPackagesEnabled(packageNames: Set<string>, enabled: boolean) {
    const packages = this.config.get('plugins') as YAMLSeq<
      YAMLMap<string, JsonValue>
    >;
Relevance

⭐ Low

Repo storage upserts flags without clearing other fields; no precedent for deleting legacy keys.

PR-#1556
PR-#1174

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The storage setters only set enabled and do not delete disabled. Since validation still permits
disabled, legacy configs can exist and will remain mixed after updates.

workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts[136-165]
workspaces/extensions/plugins/extensions-backend/src/validation/configValidation.ts[69-74]
workspaces/extensions/dynamic-plugins.yaml[1-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
When enabling/disabling via the storage layer, existing configs that still contain `disabled` will retain it forever because the new setters only write `enabled`. This can leave both `enabled` and `disabled` keys in the same item.

### Issue Context
The backend explicitly supports legacy `disabled` configs for backward compatibility, so the storage layer must normalize when writing.

### Fix Focus Areas
- workspaces/extensions/plugins/extensions-backend/src/installation/FileInstallationStorage.ts[136-165]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@rhdh-qodo-merge rhdh-qodo-merge Bot added enhancement New feature or request Tests labels Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant