Skip to content

[new plugin] Entity patch#8510

Open
Sarabadu wants to merge 23 commits intomainfrom
entity-patch
Open

[new plugin] Entity patch#8510
Sarabadu wants to merge 23 commits intomainfrom
entity-patch

Conversation

@Sarabadu
Copy link
Copy Markdown
Contributor

@Sarabadu Sarabadu commented Apr 10, 2026

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"]
Loading

###Key capabilities:

  • Config-driven forms — add editable fields to any entity kind/type without writing UI
    code
  • Scaffolder field extension support — reuse existing ui:field extensions exactly as in
    template.yaml
  • Custom bidirectional relations — declare relation pairs (e.g. hasTechLead / techLeadOf)
    and assign them via multi-entity pickers; the catalog processor emits both directions
    automatically
  • catalog updates via processor — saving a patch triggers refreshEntity so changes appear in
    the next processing loop
image

✔️ 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)
  • All your commits have a Signed-off-by line in the message. (more info)

@backstage-goalie
Copy link
Copy Markdown
Contributor

backstage-goalie Bot commented Apr 10, 2026

Changed Packages

Package Name Package Path Changeset Bump Current Version
@backstage-community/plugin-catalog-backend-module-entity-patch workspaces/entity-patch/plugins/catalog-backend-module-entity-patch patch v0.1.0
@backstage-community/plugin-entity-patch-backend workspaces/entity-patch/plugins/entity-patch-backend patch v0.1.0
@backstage-community/plugin-entity-patch-common workspaces/entity-patch/plugins/entity-patch-common patch v0.1.0
@backstage-community/plugin-entity-patch workspaces/entity-patch/plugins/entity-patch patch v0.1.0

@backstage-goalie
Copy link
Copy Markdown
Contributor

Thanks for the contribution!
All commits need to be DCO signed before they are reviewed. Please refer to the the DCO section in CONTRIBUTING.md or the DCO status for more info.

@Sarabadu Sarabadu force-pushed the entity-patch branch 4 times, most recently from cb3ded3 to 7eedd0d Compare April 10, 2026 23:36
@Sarabadu Sarabadu marked this pull request as ready for review April 11, 2026 08:25
@Sarabadu Sarabadu requested review from a team and backstage-service as code owners April 11, 2026 08:25
@Sarabadu Sarabadu requested a review from awanlin April 11, 2026 08:25
@Sarabadu Sarabadu marked this pull request as draft April 15, 2026 14:22
@Sarabadu Sarabadu force-pushed the entity-patch branch 8 times, most recently from 4cae1a8 to ed62d10 Compare April 17, 2026 18:42
Comment thread workspaces/entity-patch/plugins/catalog-backend-module-entity-patch/README.md Outdated
Comment thread workspaces/entity-patch/plugins/entity-patch-backend/src/service/PatchStore.ts Outdated
Comment thread workspaces/entity-patch/plugins/entity-patch-backend/src/service/router.ts Outdated
Comment thread workspaces/entity-patch/plugins/entity-patch-common/src/types.ts
@Sarabadu Sarabadu force-pushed the entity-patch branch 5 times, most recently from d33a456 to 1df6855 Compare April 19, 2026 17:39
@Sarabadu Sarabadu marked this pull request as ready for review April 19, 2026 19:53
Copilot AI review requested due to automatic review settings April 19, 2026 19:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-patch workspace 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.

Comment on lines +109 to +115
useEffect(() => {
if (!isDirty) return undefined;
const handler = (e: BeforeUnloadEvent) => {
e.preventDefault();
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
Comment on lines +62 to +97
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]);
Comment on lines +49 to +58
// ---------------------------------------------------------------------------
// 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
Comment on lines +56 to +60
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^7.0.0"
},
Comment on lines +52 to +77
/**
* 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();
Sarabadu and others added 22 commits April 20, 2026 00:40
- 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>
Copilot AI review requested due to automatic review settings April 19, 2026 22:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +56 to +72
"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"
Comment on lines +85 to +100
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants