* feat(ui): export TableBodySkeleton as public API
Export the TableBodySkeleton component so it can be used independently
of the built-in Table component. Relax the column type constraint from
ColumnConfig<T> to { id: string } for compatibility with custom column
types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
* fix(ui): use direct index instead of parsing skeleton item ID
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
* Update .changeset/export-table-body-skeleton.md
Co-authored-by: Johan Persson <johanopersson@gmail.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
* docs(ui): add TableBodySkeleton to table primitives documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
---------
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Johan Persson <johanopersson@gmail.com>
* feat(ui): widen activeTabId type to accept null
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* feat(ui): add automatic active tab detection to HeaderNav
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* feat(ui): update Header stories to demonstrate auto-detection and explicit activeTabId
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* refactor(ui): remove manual useActiveTabId from PluginHeaderAndHeader recipe
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* docs(ui): update Header docs for activeTabId auto-detection
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* chore(ui): add API report and changeset for activeTabId auto-detection
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* fix(ui): resolve relative hrefs in HeaderNav tabs
Add resolveHref to HeaderNavItemDefinition so tab links with relative
hrefs are resolved against the router context before rendering.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* chore(ui): add breaking change changeset for HeaderNav resolveHref
Signed-off-by: Johan Persson <johanopersson@gmail.com>
---------
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Replace Material UI components with Backstage UI (BUI) equivalents
across the InspectEntityDialog and all its tab pages (Overview,
Ancestry, Colocated, JSON, YAML).
- Dialog shell uses BUI Dialog, DialogHeader, DialogBody, Tabs
- Horizontal tab bar replaces vertical MUI tabs
- Card sections use BUI Card, CardHeader, CardBody
- Key-value pairs rendered as semantic dl/dt/dd elements
- Copy buttons use BUI ButtonIcon with remixicon icons
- Help links use BUI ButtonLink
- Alerts use BUI Alert
- Tags use BUI TagGroup/Tag
- Accessible live region for copy confirmation
- Proper heading hierarchy (h2 for tab pages, h3 for cards, h4 for sections)
- Added OverviewPage tests for identity rendering, link detection, and tags
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Replace humanizeEntityRef with entityPresentationApiRef in CatalogGraphCard
and CatalogGraphPage for consistent entity display via the Catalog
Presentation API. Contributes to #20955.
Signed-off-by: Matt Van Horn <matt@osc.dev>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
- Add `entityRef` field to all Location API responses, carrying the stable
entity ref (e.g. `location:default/generated-<sha1hex>`) that was
already persisted to the `location_entity_ref` column.
- Make `entityRef` filterable via `POST /locations/by-query`.
- Add `PUT /locations/:id` endpoint that updates the `type`/`target` of
an existing location and issues the corresponding delta mutation so the
catalog entity is updated in-place without changing its entity ref.
- Wire `updateLocation` through `CatalogApi`, `CatalogService`,
`CatalogClient`, `LocationService`, `LocationStore`, and their
implementations and mocks.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Every page refresh triggers the auth0 authenticator to fetch the user
profile from Auth0's /userinfo API. Auth0 enforces strict rate limits on
this endpoint, causing failures at scale.
Add a createAuth0Authenticator factory that accepts an optional
CacheService to cache profile responses with a 1-minute TTL. The module
now uses the cached variant by default. The existing auth0Authenticator
export remains available for use without caching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
Removes the long-deprecated re-exports from the /alpha entry point that
were only kept for backwards compatibility:
- catalogServiceRef (the CatalogApi-based legacy one)
- CatalogLocationsExtensionPoint / catalogLocationsExtensionPoint
- CatalogProcessingExtensionPoint / catalogProcessingExtensionPoint
- CatalogAnalysisExtensionPoint / catalogAnalysisExtensionPoint
All of these have non-alpha equivalents that should be used directly.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Allow the backend to reimplement extension point outputs at creation
time via a new extensionPointFactoryMiddleware option on createBackend().
Each middleware entry declaratively targets a specific extension point
by reference and the framework handles matching and pass-through
automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
Adds a migration that populates a new `location_entity_ref` column on the
`locations` table with the full entity ref of the corresponding
`kind: Location` entity (e.g. `location:default/generated-<sha1hex>`).
Postgres uses an unnest-based batch UPDATE; other engines use a
transaction-wrapped per-row loop.
All code paths in DefaultLocationStore that previously recomputed the hash
from type+target now read `location_entity_ref` directly from the DB row
instead. New rows written by `createLocation` and `#createLocationsByExactUrl`
have the column populated at insert time.
This is step 1 of migrating Location entity names to be based on the stable
row UUID rather than a hash of the mutable target URL.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Reflect the currently selected action in the URL hash so that users can
deep-link to a specific action on the actions page. On load the hash is
read to pre-select and scroll to the matching action.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
Migrated `@backstage/plugin-techdocs-backend` and
`@backstage/plugin-search-backend-module-techdocs` to use the stable
`catalogServiceRef` from `@backstage/plugin-catalog-node` instead of
the deprecated one from `@backstage/plugin-catalog-node/alpha`.
This also updates `CachedEntityLoader`, `DefaultTechDocsCollatorFactory`,
and the TechDocs router to use `CatalogService` (credentials-based) instead
of `CatalogApi` (token-based).
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Remove the deprecated alpha exports CatalogPermissionRuleInput,
CatalogPermissionExtensionPoint, and catalogPermissionExtensionPoint
from catalog-node, and remove the corresponding CatalogPermissionExtensionPointImpl,
addPermissions, and addPermissionRules from catalog-backend. Custom permission
rules and permissions are now registered via coreServices.permissionsRegistry.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Adds a new `Badge` component to the Backstage UI library. Badge shares the same visual appearance as `Tag` (size tokens, colors, border radius, icon slot) but renders as a plain non-interactive `<span>` with no React Aria plumbing.
Key characteristics:
- Plain DOM element — accessible text content exposed to screen readers without any role override
- Background consumer — participates in the bg context system and steps up neutral background levels (`neutral-2` → `neutral-3` → `neutral-4`) when placed inside colored containers
- Supports `icon`, `size` (`small` | `medium`, defaults to `small`), `children`, and `className` props
- Fully themeable via `BadgeDefinition`
Also includes Storybook stories and full docs-ui documentation (props table, examples, theming section, changelog).
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
When a Backstage notification is re-sent with the same scope, the
notifications backend updates the existing DB record and sets
notification.updated. Previously, the SlackNotificationProcessor always
called chat.postMessage(), creating duplicate Slack messages.
This adds database-backed scope-based update support:
- New slack_message_timestamps table to persist Slack message ts values
keyed by (scope, channel)
- After each chat.postMessage(), store the response ts in the database
- When postProcess receives a notification with updated set and a
matching stored ts, use chat.update() instead of chat.postMessage()
- Scope context is passed as parameters through the call chain to avoid
race conditions with concurrent postProcess calls
- Scheduled daily cleanup of old timestamp records (24h retention)
- New messagesUpdated metrics counter for observability
- Graceful degradation when no database is provided
- Explicitly picks only supported fields for chat.update calls
Signed-off-by: Erik Miller <erik.miller@gusto.com>
Adds startup warnings to HostDiscovery.fromConfig when backend.baseUrl
is set to a localhost address in a production environment, or when the
value is not a valid URL at all.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
- Remove JSON.stringify fallback from toError, use the same unknown
error messaging as stringifyError for all non-string/non-error values
- Add try/catch to protect against values that throw on string
conversion (e.g. null-prototype objects, symbols)
- Fix no-op `void toError(err)` in DeleteEntityConfirmationDialog
- Fix `${err}` producing [object Object] in UrlReaderProcessor
- Fix double toError call in openStackSwift
- Update JSDoc to accurately describe the behavior
- Add tests for throwing toString and circular objects
- Add changeset for all refactored packages
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
Add a `toError` utility function to `@backstage/errors` that converts
unknown values to `ErrorLike` objects. If the value is already error-like
it is returned as-is. Strings are used directly as the error message, and
other values are stringified with a fallback to JSON.stringify to avoid
unhelpful `[object Object]` messages.
Non-error causes passed to `CustomErrorBase` are now converted and stored
using `toError` rather than discarded. Existing `assertError` call sites
across the codebase are migrated to `toError`.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor