Conversation
Changed Packages
|
|
Thanks for the contribution! |
cb3ded3 to
7eedd0d
Compare
4cae1a8 to
ed62d10
Compare
d33a456 to
1df6855
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new “Entity Patch” plugin suite that enables editing Backstage catalog entity metadata via schema-driven forms configured in app-config.yaml, with a backend API for persisting patch data and a catalog processor module to apply patches during ingestion.
Changes:
- Introduces a new
workspaces/entity-patchworkspace with frontend plugin, backend plugin, common library, and catalog backend module. - Implements patch filtering, form rendering (RJSF + scaffolder field extensions), saving flows, and backend storage/ETag support.
- Adds unit tests plus Playwright E2E coverage and dev examples/config for local end-to-end validation.
Reviewed changes
Copilot reviewed 100 out of 109 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| workspaces/entity-patch/plugins/entity-patch/package.json | Declares frontend plugin dependencies/peers (notably router peer range). |
| workspaces/entity-patch/plugins/entity-patch/src/components/EntityPatchPage/EntityPatchPage.tsx | Standalone page for patch editing with save + dirty-state handling. |
| workspaces/entity-patch/plugins/entity-patch/src/api/EntityPatchClient.ts | Frontend API client for loading/saving patch data. |
| workspaces/entity-patch/plugins/entity-patch/src/utils/patchFilter.test.ts | Unit tests for patch filter helpers. |
| workspaces/entity-patch/.changeset/funky-worlds-float.md | Changeset entry for initial release notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useEffect(() => { | ||
| if (!isDirty) return undefined; | ||
| const handler = (e: BeforeUnloadEvent) => { | ||
| e.preventDefault(); | ||
| }; | ||
| window.addEventListener('beforeunload', handler); | ||
| return () => window.removeEventListener('beforeunload', handler); |
| const patchClient = useEntityPatchClient(); | ||
|
|
||
| const allPatches = (configApi.getOptional<PatchDefinition[]>( | ||
| CONFIG_KEYS.PATCHES, | ||
| ) ?? []) as PatchDefinition[]; | ||
|
|
||
| // Load entity + initial values together so DefaultPatchesLayout mounts | ||
| // only once both are available (prevents the useState initializer from | ||
| // running with empty data before the async fetch completes). | ||
| const { | ||
| loading, | ||
| error: loadError, | ||
| value: entityData, | ||
| } = useAsync(async () => { | ||
| const entity = await catalogApi.getEntityByRef({ | ||
| namespace: namespace!, | ||
| kind: kind!, | ||
| name: name!, | ||
| }); | ||
| if (!entity) { | ||
| throw new Error(`Entity ${kind}:${namespace}/${name} not found`); | ||
| } | ||
| let initialValues: PatchesData = {}; | ||
| let initialValuesError = false; | ||
| try { | ||
| initialValues = (await patchClient.getInitialValues( | ||
| entity.kind, | ||
| entity.metadata.namespace ?? DEFAULT_NAMESPACE, | ||
| entity.metadata.name, | ||
| )) as PatchesData; | ||
| } catch { | ||
| // Backend may not be installed; start with empty form data | ||
| initialValuesError = true; | ||
| } | ||
| return { entity, initialValues, initialValuesError }; | ||
| }, [catalogApi, namespace, kind, name]); |
| // --------------------------------------------------------------------------- | ||
| // buildMenuFilter | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| describe('buildMenuFilter', () => { | ||
| it('returns false for any entity when patches list is empty', () => { | ||
| const filter = mergePatchesFilters([]); | ||
| expect(filter(groupEntity)).toBe(false); | ||
| expect(filter(componentEntity)).toBe(false); | ||
| }); |
| '@backstage-community/plugin-entity-patch': patch | ||
| --- | ||
|
|
||
| Initial entity-patch version a plugin for manual entity metadata |
| "peerDependencies": { | ||
| "react": "^18.0.0", | ||
| "react-dom": "^18.0.0", | ||
| "react-router-dom": "^7.0.0" | ||
| }, |
| /** | ||
| * Fetches the current values for all patches on an entity, pre-populated | ||
| * from the entity's current catalog field values via the configured mapping. | ||
| * Returns `{}` if the backend is unavailable (graceful degradation). | ||
| */ | ||
| async getInitialValues( | ||
| kind: string, | ||
| namespace: string, | ||
| name: string, | ||
| ): Promise<PatchesData> { | ||
| const base = await this.baseUrl(); | ||
| const response = await this.fetchApi.fetch( | ||
| `${base}/values/${this.encodeEntityParams( | ||
| namespace, | ||
| kind, | ||
| name, | ||
| )}?fillFromEntity=true`, | ||
| ); | ||
| if (!response.ok) { | ||
| throw new Error( | ||
| `Failed to fetch initial values (${ | ||
| response.status | ||
| }): ${await response.text()}`, | ||
| ); | ||
| } | ||
| return response.json(); |
- New plugin workspace: workspaces/entity-patch - Core plugin: @backstage-community/plugin-entity-patch - RJSF-based patch forms with per-section schema support - Custom field extension support (scaffolder-compatible) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
Add docs/ folder with four annotated screenshots: - context-menu.png: Edit Patch entry in the entity context menu - patch-dialog.png: multi-section patch form dialog - patch-dialog-validation.png: inline validation errors on blur - standalone-page.png: standalone route with entity context header Reorganise README to lead with a visual How it works section that walks through the user journey before the installation/config details. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
… frontend client
- Add @backstage-community/plugin-entity-patch-backend package:
- GET /health endpoint (unauthenticated)
- GET /values/:namespace/:kind/:name - returns initial form values by merging
catalog entity field values (via mapping config) with any stored DB overlay
- POST /patches/:namespace/:kind/:name - persists submitted form data per entity+patch
- PatchStore: Knex-backed store with upsert and findByEntityRef
- EntityValueExtractor: dot-path extraction from catalog entities via mapping config
- Knex migration for entity_patches table
- Zod validation on POST body
- 11 tests passing (7 router integration + 6 EntityValueExtractor unit)
- Add EntityPatchClient to frontend plugin and wire up both plugin.tsx and
EntityPatchPage.tsx to pre-populate forms with catalog+DB values and save
patches on form submit. Backend is optional - errors fall back to empty data.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
- router.ts: replace ConfigService (non-existent) with RootConfigService, import Config from @backstage/config for explicit patch array typing, use getOptional() cast instead of generic get<T>() to avoid untyped call error - plugin.tsx: destructure kind from entity root (entity.kind) not entity.metadata since EntityMeta does not carry kind — fixes two TS2345 errors - dev/index.tsx: wrap FormFieldBlueprint.make() result in createFrontendModule so it satisfies FrontendFeature type expected by createDevApp features array All 43 tests passing. yarn tsc exits clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
- Add new package `@backstage-community/plugin-catalog-backend-module-entity-patch` - CatalogProcessor that applies saved patch data to entities during ingestion - Uses CatalogProcessorCache (30min TTL, configurable) to avoid redundant API calls - Applies values via lodash.set using reverse field→entityPath mapping - Graceful no-op on 404 or network errors - 8 passing tests covering: no-match, apply values, cache hit/miss, 404, error, unmapped field - Refactor GET /values in entity-patch-backend - Default: returns raw DB-stored data only (fast, for processor use) - With `?fillFromEntity=true`: also does catalog lookup + mapping extraction (for frontend forms) - EntityPatchClient.getInitialValues() now passes `?fillFromEntity=true` - Add router test for raw mode (no param) + reset mockStore between tests (17 tests pass) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…scaffolder Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
- Add 11 e2e tests covering dialog open/close, editing/saving, validation, and unsaved-changes warning flow - Add playwright.config.ts at workspace root (port 3010, reuseExistingServer) - Tests live in plugins/entity-patch/tests/ (repo convention) - Add polling retry in gotoEntity() to handle cold-start catalog warm-up - Enable playwrightTests in bcp.json for CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…PatchClient Extract patch logic and HTTP/cache concerns out of EntityPatchProcessor into focused, independently testable classes: - EntityPatcher: pure data transforms (applyScalars, resolveRelations). Pre-compiles patch configs at construction time into typed ScalarPatchMapping / RelationPatchMapping arrays with pre-baked matchesEntity predicates (using empty filter = always-true). No I/O — takes patchData, returns patched entity or relation list. - EntityPatchClient: owns all HTTP and ETag cache logic. Single public method getPatchData(entity, cache) handles auth token acquisition, If-None-Match conditional requests, and CatalogProcessorCache read/write. - EntityPatchProcessor: now a thin lifecycle adapter — constructs the two classes and orchestrates them in pre/postProcessEntity. Also adds unit tests for both new classes (EntityPatcher.test.ts, EntityPatchClient.test.ts) covering scalar/relation mapping, filter predicates, Nunjucks templates, ETag caching, and error handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…s, and validation modules - Extract all interface definitions into types.ts (PatchProperty, PatchSection, PatchDefinition, RelationPair, PatchConfig) - Extract mapping utilities into mappingUtils.ts (isMappingTemplate, flattenMapping, buildRelationPairs, buildPatchConfigs) - Extract zod schemas and validatePatchConfig into validation.ts - Replace index.ts with re-exports — public API unchanged, no consumer changes needed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
… cycle - preProcessEntity now stores fetched patch data in an instance-level Map (cycleData) keyed by entityRef, then clears it immediately after postProcessEntity consumes it — eliminating the second network call that postProcessEntity previously made independently - Fix EntityPatchClient: re-write cache entry on 304 (matching the canonical UrlReaderProcessor pattern) so the entry explicitly survives to the next cycle rather than relying on the implicit existingState fallback - Update tests to call preProcessEntity before postProcessEntity (reflecting the real orchestrator order) and assert the new 304 re-write behaviour Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…nify DB query in router - Always call findWithLatestUpdatedAt for a single unified DB query - Extract mergeWithCatalog() for the fillFromEntity code path - Remove configHelpers.ts shim — import directly from entity-patch-common Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…ied class
- Single EntityValueExtractor holds all patches internally (not one per patch)
- fromConfig(config, { catalogService }) is the only entry point — parses
buildRelationPairs and buildPatchConfigs internally, owns catalog fetch
- getValues(entityRef, credentials) fetches entity and extracts all patches
- mergeWithCatalog(entityRef, dbOverlay, credentials) combines catalog base
with DB overlay (DB wins per field, filter already applied via getValues)
- forPatch() test helper removed — tests go through fromConfig with
mockServices.rootConfig to keep a single real entry point
- Switch from CatalogClient to CatalogService — passes credentials directly,
no manual token exchange; removes auth and discovery from RouterOptions
- PatchExtractor interface deleted — name/filter were internal, now gone
- router.ts reduced to a thin HTTP layer; all catalog logic lives in extractor
- plugin.ts uses catalogServiceRef, drops auth/discovery deps
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…h CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…ing bracket notation - Replace custom error handler in router.ts with MiddlewareFactory.error() and move @backstage/backend-defaults to prod dependencies - Fix 'inverted mapping' wording in EntityValueExtractor test description - Update section headers in types.ts to clarify raw vs resolved config types - Fix mapping direction in README examples (entity path is the key, field name is the value) — was backwards in both entity-patch and catalog-backend-module READMEs - Add Nunjucks template mapping examples to both READMEs, showing how to compose annotation values from multiple form fields (e.g. github project slug) - Fix flattenMapping to produce bracket notation for nested YAML keys that contain dots (e.g. 'github.com/project-slug' -> ["github.com/project-slug"]), preventing lodash get/set from misinterpreting dotted segments as nested paths - Add tests for flattenMapping bracket notation (single and multiple dotted keys) - Add EntityPatcher test for writing to dotted annotation keys Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…Params, constants - Extract PatchStore.toTimestamp() private static helper to deduplicate the 'instanceof Date ? toISOString() : String()' expression used twice in findWithLatestUpdatedAt - Extract EntityPatchClient.encodeEntityParams() private method to deduplicate the namespace/kind/name URL encoding repeated in getInitialValues and savePatch - Add entity-patch-common/src/constants.ts with CONFIG_KEYS (PATCHES, RELATIONS), RELATION_KEY_PREFIX, and DEFAULT_NAMESPACE — all exported as @public - Replace 'entityPatch.patches' / 'entityPatch.relations' literals with CONFIG_KEYS throughout mappingUtils, validation, plugin.tsx, EntityPatchPage - Replace 'relations.' string literal with RELATION_KEY_PREFIX in EntityValueExtractor and EntityPatcher - Replace namespace ?? 'default' with DEFAULT_NAMESPACE in plugin.tsx, EntityPatchPage, and catalog-backend-module EntityPatchClient - Update API reports for the 3 new public exports Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…nents Extract duplicated API client construction and save logic: - Add useEntityPatchClient() hook: memoises EntityPatchClient using discoveryApiRef + fetchApiRef; replaces duplicate useMemo blocks in plugin.tsx and EntityPatchPage.tsx. - Add usePatchSave(onSuccess?) hook: wraps any save function with success/error toast notifications and an isSaving flag; used in both PatchEditDialog and EntityPatchPage. Uses a ref to always call the latest onSuccess callback without causing stale closure issues. - Add LoadErrorAlert component: self-dismissing danger alert shown when initial patch data fails to load; replaces identical inline Alert blocks in PatchEditDialog and EntityPatchPage. - Add UnsavedWarningAlert component: warning alert with Keep editing / Discard changes actions; extracted from PatchEditDialog. - Update Save buttons to also disable while isSaving is true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
…a form - Delete PatchSection, PatchGroup, DefaultPatchesLayout components - Merge section fields (title, description, required, properties, errorMessage) directly onto PatchDefinition — no more sections[] array - Support nested object properties for visual grouping (component-metadata) - Use lodash get/set for dot-notation mapping values (backend + extractor) - Update all tests, API reports, and README docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
- patchFilter.test.ts: rename describe block to match exported function name
('buildMenuFilter' → 'mergePatchesFilters')
- EntityPatchPage.tsx: add e.returnValue='' to beforeunload handler for
Chrome/Edge compatibility; add patchClient to useAsync dep array
- EntityPatchClient.ts: fix JSDoc for getInitialValues (throws, does not
return {} on error)
- PatchStore.ts: simplify toTimestamp signature (Date|unknown → unknown)
- types.ts: add comment explaining PatchDefinition vs PatchConfig distinction
- package.json: align react-router-dom peer dep with repo standard (^6.0.0)
- changeset: reword description to a proper sentence
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
… empty values on mount EntityPicker fields (Designers, Tech Leads) call RJSF onChange on mount to normalize undefined → [], which incorrectly set isDirty=true before any user interaction. This caused the Save button to be enabled on open for entities with empty picker fields (e.g. groups with no relations). Replace the hardcoded `isDirty: true` in handlePatchChange with a proper deep comparison against initialData, treating undefined/null/[] as equivalent empty states so field initialization is not counted as a change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
There was a problem hiding this comment.
Pull request overview
Introduces a new entity-patch plugin suite that enables editing Backstage catalog entity metadata via config-driven UI forms, with persistence handled by a backend API and application of changes performed by a catalog processor module.
Changes:
- Adds a new frontend plugin that renders patch forms (Scaffolder field extensions supported) via an entity context menu item and a standalone page.
- Adds a backend plugin that stores patch data per entity, supports ETag-based conditional reads, and triggers catalog refresh after saves.
- Adds a catalog backend module that fetches stored patch data and applies scalar updates + emits bidirectional custom relations during processing.
Reviewed changes
Copilot reviewed 100 out of 108 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| workspaces/entity-patch/tsconfig.json | Workspace TS config |
| workspaces/entity-patch/plugins/entity-patch/tests/entity-patch.spec.ts | Playwright E2E coverage |
| workspaces/entity-patch/plugins/entity-patch/tests/EntityPatchPage.ts | E2E page object model |
| workspaces/entity-patch/plugins/entity-patch/src/utils/saveAllPatches.ts | Persist multiple patch payloads |
| workspaces/entity-patch/plugins/entity-patch/src/utils/patchFilter.ts | Patch-to-entity filter helpers |
| workspaces/entity-patch/plugins/entity-patch/src/utils/patchFilter.test.ts | Unit tests for filter helpers |
| workspaces/entity-patch/plugins/entity-patch/src/utils/hasErrors.ts | Validity-map helper |
| workspaces/entity-patch/plugins/entity-patch/src/setupTests.ts | Jest DOM setup |
| workspaces/entity-patch/plugins/entity-patch/src/routes.ts | Route ref definition |
| workspaces/entity-patch/plugins/entity-patch/src/plugin.tsx | Frontend plugin + menu item |
| workspaces/entity-patch/plugins/entity-patch/src/plugin.test.ts | Plugin export test |
| workspaces/entity-patch/plugins/entity-patch/src/index.ts | Package entrypoint |
| workspaces/entity-patch/plugins/entity-patch/src/hooks/usePatchSave.ts | Save wrapper + toasts |
| workspaces/entity-patch/plugins/entity-patch/src/hooks/useEntityPatchClient.ts | Frontend API client hook |
| workspaces/entity-patch/plugins/entity-patch/src/components/UnsavedWarningAlert.tsx | Unsaved changes UI |
| workspaces/entity-patch/plugins/entity-patch/src/components/PatchesLayout/PatchesLayout.tsx | Multi-patch form layout |
| workspaces/entity-patch/plugins/entity-patch/src/components/PatchesLayout/PatchForm.tsx | Single patch form component |
| workspaces/entity-patch/plugins/entity-patch/src/components/PatchesLayout/FormConfig.tsx | Shared form config context |
| workspaces/entity-patch/plugins/entity-patch/src/components/PatchEditDialog/index.ts | Dialog barrel export |
| workspaces/entity-patch/plugins/entity-patch/src/components/PatchEditDialog/PatchEditDialog.tsx | Edit dialog UI + actions |
| workspaces/entity-patch/plugins/entity-patch/src/components/LoadErrorAlert.tsx | Initial-data load error UI |
| workspaces/entity-patch/plugins/entity-patch/src/components/FieldOverrides/index.ts | Field override exports |
| workspaces/entity-patch/plugins/entity-patch/src/components/FieldOverrides/DescriptionField.tsx | Markdown description renderer |
| workspaces/entity-patch/plugins/entity-patch/src/components/EntityPatchPage/EntityPatchPage.tsx | Standalone edit page |
| workspaces/entity-patch/plugins/entity-patch/src/api/EntityPatchClient.ts | Frontend REST client |
| workspaces/entity-patch/plugins/entity-patch/src/api/EntityPatchClient.test.ts | REST client unit tests |
| workspaces/entity-patch/plugins/entity-patch/report.api.md | API extractor report |
| workspaces/entity-patch/plugins/entity-patch/package.json | Frontend plugin manifest |
| workspaces/entity-patch/plugins/entity-patch/knip-report.md | Knip report placeholder |
| workspaces/entity-patch/plugins/entity-patch/dev/index.tsx | Dev app bootstrap |
| workspaces/entity-patch/plugins/entity-patch/config.d.ts | Config schema typings |
| workspaces/entity-patch/plugins/entity-patch/README.md | Frontend plugin docs |
| workspaces/entity-patch/plugins/entity-patch/.eslintrc.js | Package eslint config |
| workspaces/entity-patch/plugins/entity-patch-common/src/validation.ts | Config validation (zod) |
| workspaces/entity-patch/plugins/entity-patch-common/src/types.ts | Shared config + resolved types |
| workspaces/entity-patch/plugins/entity-patch-common/src/mappingUtils.ts | Mapping flattening + builders |
| workspaces/entity-patch/plugins/entity-patch-common/src/index.ts | Common package exports |
| workspaces/entity-patch/plugins/entity-patch-common/src/index.test.ts | Common utils tests |
| workspaces/entity-patch/plugins/entity-patch-common/src/constants.ts | Shared constants |
| workspaces/entity-patch/plugins/entity-patch-common/report.api.md | API extractor report |
| workspaces/entity-patch/plugins/entity-patch-common/package.json | Common library manifest |
| workspaces/entity-patch/plugins/entity-patch-common/knip-report.md | Knip report placeholder |
| workspaces/entity-patch/plugins/entity-patch-common/.eslintrc.js | Package eslint config |
| workspaces/entity-patch/plugins/entity-patch-backend/src/service/router.ts | Backend REST router |
| workspaces/entity-patch/plugins/entity-patch-backend/src/service/router.test.ts | Router tests |
| workspaces/entity-patch/plugins/entity-patch-backend/src/service/PatchStore.ts | Patch DB store (Knex) |
| workspaces/entity-patch/plugins/entity-patch-backend/src/service/EntityValueExtractor.ts | Fill-from-entity extraction |
| workspaces/entity-patch/plugins/entity-patch-backend/src/service/EntityValueExtractor.test.ts | Extractor tests |
| workspaces/entity-patch/plugins/entity-patch-backend/src/plugin.ts | Backend plugin registration |
| workspaces/entity-patch/plugins/entity-patch-backend/src/index.ts | Backend package entrypoint |
| workspaces/entity-patch/plugins/entity-patch-backend/report.api.md | API extractor report |
| workspaces/entity-patch/plugins/entity-patch-backend/package.json | Backend plugin manifest |
| workspaces/entity-patch/plugins/entity-patch-backend/migrations/001_create_entity_patches.js | DB migration |
| workspaces/entity-patch/plugins/entity-patch-backend/knip-report.md | Knip report placeholder |
| workspaces/entity-patch/plugins/entity-patch-backend/examples/org.yaml | Dev catalog org entities |
| workspaces/entity-patch/plugins/entity-patch-backend/examples/entities.yaml | Dev catalog component entities |
| workspaces/entity-patch/plugins/entity-patch-backend/dev/index.ts | Dev backend bootstrap |
| workspaces/entity-patch/plugins/entity-patch-backend/README.md | Backend plugin docs |
| workspaces/entity-patch/plugins/entity-patch-backend/.eslintrc.js | Package eslint config |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/projectSlug/index.ts | Filter export |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/projectSlug/filter.ts | Nunjucks filter implementation |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/pick/index.ts | Filter export |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/pick/filter.ts | Nunjucks filter implementation |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/parseRepoUrl/index.ts | Filter export |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/parseRepoUrl/filter.ts | Nunjucks filter implementation |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/parseEntityRef/index.ts | Filter export |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/parseEntityRef/filter.ts | Nunjucks filter implementation |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/templating/filters/createDefaultFilters.ts | Default filter registry |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/processor/index.ts | Processor exports |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/processor/EntityPatcher.ts | Patch application + relations |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/processor/EntityPatcher.test.ts | Patcher unit tests |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/processor/EntityPatchProcessor.ts | Catalog processor implementation |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/processor/EntityPatchClient.ts | Processor-side API client |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/processor/EntityPatchClient.test.ts | Processor client tests |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/module.ts | Backend module registration |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/src/index.ts | Module entrypoint |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/report.api.md | API extractor report |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/package.json | Catalog module manifest |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/knip-report.md | Knip report placeholder |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/README.md | Catalog module docs |
| workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/.eslintrc.js | Package eslint config |
| workspaces/entity-patch/plugins/README.md | Workspace plugins folder note |
| workspaces/entity-patch/playwright.config.ts | Playwright config |
| workspaces/entity-patch/package.json | Workspace dev tooling config |
| workspaces/entity-patch/bcp.json | BCP automation config |
| workspaces/entity-patch/backstage.json | Backstage version pin |
| workspaces/entity-patch/app-config.yaml | Dev app config + patch examples |
| workspaces/entity-patch/README.md | Workspace overview docs |
| workspaces/entity-patch/.yarnrc.yml | Yarn plugin config |
| workspaces/entity-patch/.prettierignore | Prettier ignore rules |
| workspaces/entity-patch/.gitignore | Git ignore rules |
| workspaces/entity-patch/.eslintrc.js | Workspace eslint config |
| workspaces/entity-patch/.eslintignore | Eslint ignore rules |
| workspaces/entity-patch/.dockerignore | Docker ignore rules |
| workspaces/entity-patch/.changeset/funky-worlds-float.md | Initial changeset |
| workspaces/entity-patch/.changeset/config.json | Changesets config |
| workspaces/entity-patch/.changeset/README.md | Changesets docs |
| .github/CODEOWNERS | Adds workspace codeowners |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "peerDependencies": { | ||
| "react": "^18.0.0", | ||
| "react-dom": "^18.0.0", | ||
| "react-router-dom": "^6.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@backstage/cli": "backstage:^", | ||
| "@backstage/frontend-dev-utils": "backstage:^", | ||
| "@backstage/frontend-test-utils": "backstage:^", | ||
| "@backstage/plugin-catalog": "backstage:^", | ||
| "@backstage/plugin-scaffolder": "backstage:^", | ||
| "@playwright/test": "^1.59.1", | ||
| "@testing-library/jest-dom": "^6.9.1", | ||
| "@testing-library/react": "^14.3.1", | ||
| "react": "^18.3.1", | ||
| "react-dom": "^18.3.1", | ||
| "react-router-dom": "^7.14.1" |
| const handleChange = async (data: PatchFormData | undefined) => { | ||
| const next = data ?? {}; | ||
| setFormData(next); | ||
| setIsValidating(true); | ||
|
|
||
| const { errors: schemaErrors } = validator.validateFormData(next, schema); | ||
| const schemaValid = schemaErrors.length === 0; | ||
| setHasSchemaErrors(!schemaValid); | ||
|
|
||
| try { | ||
| const validation = await validate(patch, next); | ||
| setExtraErrors(validation as unknown as ErrorSchema); | ||
| onChange(next, !hasRjsfErrors(validation) && schemaValid); | ||
| } finally { | ||
| setIsValidating(false); | ||
| } |
When the user has scrolled down in a long form and clicks Close with unsaved changes, the UnsavedWarningAlert at the top of DialogContent was out of view. Now the dialog scrolls smoothly back to the top whenever the warning is triggered. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
Hey, I just made a Pull Request!
Entity Patch is a new plugin that lets teams update catalog entity metadata directly from the Backstage UI — no YAML editing, no deployments, no custom frontend code required.
Patch forms are declared entirely in app-config.yaml using the same as Backstage scaffolder templates, so any existing field extension (EntityPicker, OwnerPicker, MultiEntityPicker, etc.) works out of the box. An Edit Patch item appears in the entity context menu for any entity that matches a configured filter, and the form opens pre-populated with the current stored values.
How it works
flowchart TD Maintainer(["🔧 Maintainer"]) User(["👤 User"]) Maintainer -->|"defines patch schemas,\nfilters & field mappings"| Config["app-config.yaml"] Config --> Frontend["Frontend\nEdit Entity option\nshown on matching entities"] User --> Frontend Frontend -->|"fills & saves\nthe form"| Processor["Catalog Processor"] Processor -->|"applies mapping\nto entity fields & relations"| Entity["Catalog Entity"]###Key capabilities:
code
template.yaml
and assign them via multi-entity pickers; the catalog processor emits both directions
automatically
the next processing loop
✔️ Checklist
Signed-off-byline in the message. (more info)