From 0410fc9ce605f1495870eaee1715a33e706cf58b Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 30 Apr 2024 11:49:36 +0300 Subject: [PATCH 001/393] feat: allow setting notification read from web/snackbar when opening link from snackbar or web notification, the notification can be automatically set as read like in the notifications page Signed-off-by: Heikki Hellgren --- .changeset/ten-penguins-roll.md | 5 ++++ plugins/notifications/api-report.md | 6 ++++- .../NotificationsSideBarItem.tsx | 23 +++++++++++++++++-- .../src/hooks/useWebNotifications.ts | 23 ++++++++++++++++--- 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 .changeset/ten-penguins-roll.md diff --git a/.changeset/ten-penguins-roll.md b/.changeset/ten-penguins-roll.md new file mode 100644 index 0000000000..60e0ae773a --- /dev/null +++ b/.changeset/ten-penguins-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Allow setting notification as read when opening snackbar or web notification link diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 9240bdb1c2..2547204c7b 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -103,6 +103,7 @@ export const NotificationsSidebarItem: (props?: { titleCounterEnabled?: boolean; snackbarEnabled?: boolean; snackbarAutoHideDuration?: number | null; + markAsReadOnLinkOpen?: boolean; className?: string; icon?: IconComponent; text?: string; @@ -182,7 +183,10 @@ export function useTitleCounter(): { }; // @public (undocumented) -export function useWebNotifications(enabled: boolean): { +export function useWebNotifications( + enabled: boolean, + markAsReadOnLinkOpen: boolean, +): { sendWebNotification: (options: { id: string; title: string; diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 7b55c63747..6e1f7c7d56 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -84,6 +84,7 @@ export const NotificationsSidebarItem = (props?: { titleCounterEnabled?: boolean; snackbarEnabled?: boolean; snackbarAutoHideDuration?: number | null; + markAsReadOnLinkOpen?: boolean; className?: string; icon?: IconComponent; text?: string; @@ -95,6 +96,7 @@ export const NotificationsSidebarItem = (props?: { titleCounterEnabled = true, snackbarEnabled = true, snackbarAutoHideDuration = 10000, + markAsReadOnLinkOpen = false, icon = NotificationsIcon, text = 'Notifications', ...restProps @@ -103,6 +105,7 @@ export const NotificationsSidebarItem = (props?: { titleCounterEnabled: true, snackbarEnabled: true, snackbarAutoHideDuration: 10000, + markAsReadOnLinkOpen: false, }; const { loading, error, value, retry } = useNotificationsApi(api => @@ -114,7 +117,10 @@ export const NotificationsSidebarItem = (props?: { const notificationsRoute = useRouteRef(rootRouteRef); // TODO: Do we want to add long polling in case signals are not available const { lastSignal } = useSignal('notifications'); - const { sendWebNotification } = useWebNotifications(webNotificationsEnabled); + const { sendWebNotification } = useWebNotifications( + webNotificationsEnabled, + markAsReadOnLinkOpen, + ); const [refresh, setRefresh] = React.useState(false); const { setNotificationCount } = useTitleCounter(); @@ -126,6 +132,19 @@ export const NotificationsSidebarItem = (props?: { component={Link} to={notification.payload.link ?? notificationsRoute()} onClick={() => { + if (markAsReadOnLinkOpen) { + notificationsApi + .updateNotifications({ + ids: [notification.id], + read: true, + }) + .catch(() => { + alertApi.post({ + message: 'Failed to mark notification as read', + severity: 'error', + }); + }); + } closeSnackbar(snackBarId); }} > @@ -156,7 +175,7 @@ export const NotificationsSidebarItem = (props?: { return { action }; }, - [notificationsRoute, notificationsApi, alertApi], + [notificationsRoute, markAsReadOnLinkOpen, notificationsApi, alertApi], ); useEffect(() => { diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index 728d992130..92a0a9b806 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -15,14 +15,19 @@ */ import { useCallback, useEffect, useState } from 'react'; import { rootRouteRef } from '../routes'; -import { useRouteRef } from '@backstage/core-plugin-api'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { useNavigate } from 'react-router-dom'; +import { notificationsApiRef } from '../api'; /** @public */ -export function useWebNotifications(enabled: boolean) { +export function useWebNotifications( + enabled: boolean, + markAsReadOnLinkOpen: boolean, +) { const [webNotificationPermission, setWebNotificationPermission] = useState('default'); const notificationsRoute = useRouteRef(rootRouteRef); + const notificationsApi = useApi(notificationsApiRef); const navigate = useNavigate(); useEffect(() => { @@ -57,6 +62,12 @@ export function useWebNotifications(enabled: boolean) { event.preventDefault(); if (options.link) { window.open(options.link, '_blank'); + if (markAsReadOnLinkOpen) { + notificationsApi.updateNotifications({ + ids: [options.id], + read: true, + }); + } } else { navigate(notificationsRoute()); } @@ -65,7 +76,13 @@ export function useWebNotifications(enabled: boolean) { return notification; }, - [webNotificationPermission, navigate, notificationsRoute], + [ + webNotificationPermission, + markAsReadOnLinkOpen, + notificationsApi, + navigate, + notificationsRoute, + ], ); return { sendWebNotification }; From a880e941aaf1382189169b3416df51ec4b2c0b6c Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 30 May 2024 15:45:38 +0300 Subject: [PATCH 002/393] fix: change notification hooks to internal Signed-off-by: Heikki Hellgren --- plugins/notifications/api-report.md | 18 ------------------ .../notifications/src/hooks/useTitleCounter.ts | 2 +- .../src/hooks/useWebNotifications.ts | 2 +- plugins/notifications/src/index.ts | 2 +- 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 2547204c7b..c431664883 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -177,23 +177,5 @@ export function useNotificationsApi( value: T; }; -// @public (undocumented) -export function useTitleCounter(): { - setNotificationCount: (newCount: number) => void; -}; - -// @public (undocumented) -export function useWebNotifications( - enabled: boolean, - markAsReadOnLinkOpen: boolean, -): { - sendWebNotification: (options: { - id: string; - title: string; - description: string; - link?: string; - }) => Notification | null; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/notifications/src/hooks/useTitleCounter.ts b/plugins/notifications/src/hooks/useTitleCounter.ts index 0793cd58d6..399bfb84f4 100644 --- a/plugins/notifications/src/hooks/useTitleCounter.ts +++ b/plugins/notifications/src/hooks/useTitleCounter.ts @@ -25,7 +25,7 @@ const throttledSetTitle = throttle((shownTitle: string) => { document.title = shownTitle; }, 100); -/** @public */ +/** @internal */ export function useTitleCounter() { const [title, setTitle] = useState(document.title); const [count, setCount] = useState(0); diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index 92a0a9b806..3fbd3c5496 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -19,7 +19,7 @@ import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { useNavigate } from 'react-router-dom'; import { notificationsApiRef } from '../api'; -/** @public */ +/** @internal */ export function useWebNotifications( enabled: boolean, markAsReadOnLinkOpen: boolean, diff --git a/plugins/notifications/src/index.ts b/plugins/notifications/src/index.ts index bb5d76a0cf..25565f1ae1 100644 --- a/plugins/notifications/src/index.ts +++ b/plugins/notifications/src/index.ts @@ -15,5 +15,5 @@ */ export { notificationsPlugin, NotificationsPage } from './plugin'; export * from './api'; -export * from './hooks'; +export { useNotificationsApi } from './hooks'; export * from './components'; From aa2d3e302acff82c078dcc40dd75fe17144fc5ef Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 30 May 2024 21:14:02 +0300 Subject: [PATCH 003/393] feat: make default functionality to mark notification read when opening link Signed-off-by: Heikki Hellgren --- .changeset/ten-penguins-roll.md | 4 +-- plugins/notifications/api-report.md | 1 - .../NotificationsSideBarItem.tsx | 13 ++++----- .../src/hooks/useWebNotifications.ts | 29 ++++++------------- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/.changeset/ten-penguins-roll.md b/.changeset/ten-penguins-roll.md index 60e0ae773a..152bb1524b 100644 --- a/.changeset/ten-penguins-roll.md +++ b/.changeset/ten-penguins-roll.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-notifications': patch +'@backstage/plugin-notifications': minor --- -Allow setting notification as read when opening snackbar or web notification link +By default, set notification as read when opening snackbar or web notification link diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index c431664883..5e06ae2616 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -103,7 +103,6 @@ export const NotificationsSidebarItem: (props?: { titleCounterEnabled?: boolean; snackbarEnabled?: boolean; snackbarAutoHideDuration?: number | null; - markAsReadOnLinkOpen?: boolean; className?: string; icon?: IconComponent; text?: string; diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 6e1f7c7d56..457893b016 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -84,7 +84,6 @@ export const NotificationsSidebarItem = (props?: { titleCounterEnabled?: boolean; snackbarEnabled?: boolean; snackbarAutoHideDuration?: number | null; - markAsReadOnLinkOpen?: boolean; className?: string; icon?: IconComponent; text?: string; @@ -96,7 +95,6 @@ export const NotificationsSidebarItem = (props?: { titleCounterEnabled = true, snackbarEnabled = true, snackbarAutoHideDuration = 10000, - markAsReadOnLinkOpen = false, icon = NotificationsIcon, text = 'Notifications', ...restProps @@ -105,7 +103,6 @@ export const NotificationsSidebarItem = (props?: { titleCounterEnabled: true, snackbarEnabled: true, snackbarAutoHideDuration: 10000, - markAsReadOnLinkOpen: false, }; const { loading, error, value, retry } = useNotificationsApi(api => @@ -117,9 +114,8 @@ export const NotificationsSidebarItem = (props?: { const notificationsRoute = useRouteRef(rootRouteRef); // TODO: Do we want to add long polling in case signals are not available const { lastSignal } = useSignal('notifications'); - const { sendWebNotification } = useWebNotifications( + const { sendWebNotification, requestUserPermission } = useWebNotifications( webNotificationsEnabled, - markAsReadOnLinkOpen, ); const [refresh, setRefresh] = React.useState(false); const { setNotificationCount } = useTitleCounter(); @@ -132,7 +128,7 @@ export const NotificationsSidebarItem = (props?: { component={Link} to={notification.payload.link ?? notificationsRoute()} onClick={() => { - if (markAsReadOnLinkOpen) { + if (notification.payload.link) { notificationsApi .updateNotifications({ ids: [notification.id], @@ -175,7 +171,7 @@ export const NotificationsSidebarItem = (props?: { return { action }; }, - [notificationsRoute, markAsReadOnLinkOpen, notificationsApi, alertApi], + [notificationsRoute, notificationsApi, alertApi], ); useEffect(() => { @@ -278,6 +274,9 @@ export const NotificationsSidebarItem = (props?: { )} { + requestUserPermission(); + }} hasNotifications={!error && !!unreadCount} text={text} icon={icon} diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index 3fbd3c5496..ec120a614d 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -13,24 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; import { rootRouteRef } from '../routes'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { useNavigate } from 'react-router-dom'; import { notificationsApiRef } from '../api'; /** @internal */ -export function useWebNotifications( - enabled: boolean, - markAsReadOnLinkOpen: boolean, -) { +export function useWebNotifications(enabled: boolean) { const [webNotificationPermission, setWebNotificationPermission] = useState('default'); const notificationsRoute = useRouteRef(rootRouteRef); const notificationsApi = useApi(notificationsApiRef); const navigate = useNavigate(); - useEffect(() => { + const requestUserPermission = useCallback(() => { if ( enabled && 'Notification' in window && @@ -62,12 +59,10 @@ export function useWebNotifications( event.preventDefault(); if (options.link) { window.open(options.link, '_blank'); - if (markAsReadOnLinkOpen) { - notificationsApi.updateNotifications({ - ids: [options.id], - read: true, - }); - } + notificationsApi.updateNotifications({ + ids: [options.id], + read: true, + }); } else { navigate(notificationsRoute()); } @@ -76,14 +71,8 @@ export function useWebNotifications( return notification; }, - [ - webNotificationPermission, - markAsReadOnLinkOpen, - notificationsApi, - navigate, - notificationsRoute, - ], + [webNotificationPermission, notificationsApi, navigate, notificationsRoute], ); - return { sendWebNotification }; + return { sendWebNotification, requestUserPermission }; } From acea14b422f1acec216c16f0f62b13ec72a4ad2b Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 4 Jun 2024 16:08:51 -0500 Subject: [PATCH 004/393] docs: add BEP for audit log Signed-off-by: Paul Schultz --- beps/0009-audit-log/README.md | 298 ++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 beps/0009-audit-log/README.md diff --git a/beps/0009-audit-log/README.md b/beps/0009-audit-log/README.md new file mode 100644 index 0000000000..2693dd30eb --- /dev/null +++ b/beps/0009-audit-log/README.md @@ -0,0 +1,298 @@ +--- +title: Audit Log +status: provisional +authors: + - '@schultzp2020' +owners: +project-areas: + - core +creation-date: 2024-06-04 +--- + +# BEP: Audit Log + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/23950) + +- [BEP: Audit Log](#bep-audit-log) + - [Summary](#summary) + - [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Proposal](#proposal) + - [Design Details](#design-details) + - [Winston Configuration Changes](#winston-configuration-changes) + - [Data Model for Audit Logs](#data-model-for-audit-logs) + - [Actor Details Interface](#actor-details-interface) + - [Audit Request/Response Interfaces](#audit-requestresponse-interfaces) + - [Audit Log Status Interfaces](#audit-log-status-interfaces) + - [AuditLogger Interface](#auditlogger-interface) + - [Release Plan](#release-plan) + - [Dependencies](#dependencies) + - [Alternatives](#alternatives) + +## Summary + +This feature introduces a dedicated system for recording critical security-related actions and events within Backstage. By maintaining a distinct audit log stream, organizations benefit from enhanced security, improved regulatory compliance, efficient incident response, and ensured data integrity. + +## Motivation + +- Strengthen security by tracking user authentication, authorization, data access, and configuration changes. +- Facilitate adherence to regulatory requirements through logging security-sensitive operations. +- Enable efficient forensic analysis and investigation of security incidents. +- Uphold the integrity of critical audit data by implementing robust access controls and tamper-proof measures. + +### Goals + +- Implement a separate audit log event stream for recording security-critical events. +- Defining a specific data format for audit logs. +- Ensure adherence to regulatory compliance requirements through comprehensive audit logging. + +### Non-Goals + +- Implementing mechanisms for log storage, analysis, or visualization. +- Addressing security aspects of log storage and access control beyond initial separation from regular logs. + +Both of these non-goals can be implemented as separate plugins. + +## Proposal + +The proposal introduces two key changes to implement a separate audit log event stream in Backstage. First, we recommend modifying Backstage's Winston configuration to create a distinct channel specifically for audit logs. This can be achieved by extending the [existing configuration file](https://github.com/backstage/backstage/blob/master/packages/backend-app-api/src/logging/WinstonLogger.ts). A reference implementation for such a configuration can be found [here](https://github.com/janus-idp/backstage-showcase/blob/main/packages/backend/src/logger/customLogger.ts). Separating the configuration ensures clear distinction between regular application logs and critical security events. + +Secondly, the proposal suggests creating a new shared package within the Backstage ecosystem. This package would define a standardized data format for audit logs. The format would include mandatory fields relevant for regulatory compliance and security investigations, such as actor, IP address, timestamp, and event details. The package would also provide helper functions to simplify logging audit events throughout the Backstage application. A reference implementation for such a package can be found [here](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/audit-log-node). This standardized format would streamline analysis and investigation of security-related events. + +Overall, this approach offers several benefits. By separating the configuration, security-critical events are clearly distinguished for improved monitoring and analysis. The standardized data format within the shared package would ensure consistency and facilitate compliance with regulations. Finally, the helper functions within the package would simplify the process of logging audit events. + +## Design Details + +### Winston Configuration Changes + +The proposal involves modifying Backstage's logging configuration using the Winston library. This modification creates a separate channel specifically for audit logs. + +```ts +/** + * A default formatting function is defined. This function adds timestamps, error details, and other relevant information to all logs. + */ +const defaultFormat = winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + winston.format.errors({ stack: true }), + winston.format.splat(), +); + +const auditLogFormat = winston.format((info, opts) => { + const { isAuditLog, ...newInfo } = info; + + if (isAuditLog) { + /** + * If the flag `isAuditLog` is present and set to true, the entire message is included in the audit log format. + */ + return opts.isAuditLog ? info : false; + } + + /** + * If the flag `isAuditLog` is absent or set to false, the message is excluded from the audit log but included in the regular application log. + */ + return !opts.isAuditLog ? newInfo : false; +}); + +/** + * Two separate transport configurations are created: one for regular logs and one for audit logs. JSON formatting was chosen for easier parsing by logging tools. + */ +const transports = { + log: [ + new winston.transports.Console({ + format: winston.format.combine( + auditLogFormat({ isAuditLog: false }), + defaultFormat, + winston.format.json(), + ), + }), + ], + auditLog: [ + new winston.transports.Console({ + format: winston.format.combine( + auditLogFormat({ isAuditLog: true }), + defaultFormat, + winston.format.json(), + ), + }), + ], +}; + +const logger = WinstonLogger.create({ + meta: { + service: 'backstage', + }, + level: process.env.LOG_LEVEL ?? 'info', + format: winston.format.combine(defaultFormat, winston.format.json()), + transports: [...transports.log, ...transports.auditLog], +}); +``` + +### Data Model for Audit Logs + +To ensure consistency and facilitate regulatory compliance, the proposal suggests creating a shared package that defines a data model for audit logs. This model consists of several key components. + +#### Actor Details Interface + +This interface defines the information related to the actor who triggered the logged event. It includes fields like actor ID, IP address, hostname, and user agent. + +```ts +export type ActorDetails = { + actorId: string | null; + ip?: string; + hostname?: string; + userAgent?: string; +}; +``` + +#### Audit Request/Response Interfaces + +These interfaces define the structure of request and response data that might be included in the audit log. It's important to note that these interfaces exclude sensitive information like tokens from headers or other irrelevant details to avoid security risks. + +```ts +export type AuditRequest = { + body: any; + url: string; + method: string; + params?: any; + query?: any; +}; + +export type AuditResponse = { + status: number; + body?: any; +}; +``` + +#### Audit Log Status Interfaces + +These interfaces define the possible statuses for an audit log entry. There are three options: + +```ts +/** + * Indicates the event was successful. + */ +export type AuditLogSuccessStatus = { status: 'succeeded' }; + +/** + * Indicates the event failed and includes details about the encountered errors. + */ +export type AuditLogFailureStatus = { + status: 'failed'; + errors: ErrorLike[]; +}; + +/** + * Indicates the event failed with errors that don't perfectly fit the expected structure. + */ +export type AuditLogUnknownFailureStatus = { + status: 'failed'; + errors: unknown[]; +}; + +export type AuditLogStatus = AuditLogSuccessStatus | AuditLogFailureStatus; +``` + +#### AuditLogger Interface + +This interface defines the functionalities of an `AuditLogger` class. This class provides methods for: +- Extracting the actor ID from an Express request (if available). +- Creating detailed audit log information based on provided options. +- Logging an audit event with a specific level (info, debug, warn, or error). + +```ts +/** + * Common fields of an audit log. Note: timestamp and pluginId are automatically added at log creation. + * + * @public + */ +export type AuditLogDetails = { + actor: ActorDetails; + eventName: string; + stage: string; + request?: AuditRequest; + response?: AuditResponse; + meta: JsonValue; + isAuditLog: true; +} & AuditLogStatus; + +export type AuditLogDetailsOptions = { + eventName: string; + stage: string; + metadata?: JsonValue; + response?: AuditResponse; + actorId?: string; + request?: Request; +} & (AuditLogSuccessStatus | AuditLogUnknownFailureStatus); + +export type AuditLogOptions = { + eventName: string; + message: string; + stage: string; + level?: 'info' | 'debug' | 'warn' | 'error'; + actorId?: string; + metadata?: JsonValue; + response?: AuditResponse; + request?: Request; +} & (AuditLogSuccessStatus | AuditLogUnknownFailureStatus); + +export type AuditLoggerOptions = { + logger: LoggerService; + authService: AuthService; + httpAuthService: HttpAuthService; +}; + +export interface AuditLogger { + /** + * Processes an express request and obtains the actorId from it. Returns undefined if actorId is not obtainable. + * + * @public + */ + getActorId(request?: Request): Promise; + + /** + * Generates the audit log details to place in the metadata argument of the logger + * + * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object + * @public + */ + createAuditLogDetails( + options: AuditLogDetailsOptions, + ): Promise; + + /** + * Generates an Audit Log and logs it at the level passed by the user. + * Supports `info`, `debug`, `warn` or `error` level. Defaults to `info` if no level is passed. + * + * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object + * @public + */ + auditLog(options: AuditLogOptions): Promise; +} +``` + +## Release Plan + +WIP + + + +## Dependencies + +- `@backstage/backend-app-api` + +## Alternatives + +WIP + + From 62abf939d80dc531ae14c6e62217889413be1ad4 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Mon, 10 Jun 2024 12:32:32 -0500 Subject: [PATCH 005/393] add release plan and alternatives Signed-off-by: Paul Schultz --- beps/0009-audit-log/README.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/beps/0009-audit-log/README.md b/beps/0009-audit-log/README.md index 2693dd30eb..8f1955f72f 100644 --- a/beps/0009-audit-log/README.md +++ b/beps/0009-audit-log/README.md @@ -29,6 +29,7 @@ creation-date: 2024-06-04 - [Release Plan](#release-plan) - [Dependencies](#dependencies) - [Alternatives](#alternatives) + - [Create a Separate Winston Logger Instance](#create-a-separate-winston-logger-instance) ## Summary @@ -275,13 +276,7 @@ export interface AuditLogger { ## Release Plan -WIP - - +The release plan involves initially creating a shared audit log package. Following this, the audit log will be implemented in core packages and other plugins. The first targets should be high-priority areas, such as the scaffolder and catalog systems. Since adding the audit log will not disrupt existing functionality, the release plan is simplified. ## Dependencies @@ -289,10 +284,6 @@ If there is any particular feedback to be gathered during the rollout, this shou ## Alternatives -WIP +### Create a Separate Winston Logger Instance - +We could create a separate instance of the Winston logger. However, this approach would necessitate core packages and plugins to include an additional argument in their constructor. From c27c109921425b241d4ffbd827c7b3c795929bbf Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Mon, 10 Jun 2024 12:53:34 -0500 Subject: [PATCH 006/393] add goal Signed-off-by: Paul Schultz --- beps/{0009-audit-log => 00010-audit-log}/README.md | 1 + 1 file changed, 1 insertion(+) rename beps/{0009-audit-log => 00010-audit-log}/README.md (99%) diff --git a/beps/0009-audit-log/README.md b/beps/00010-audit-log/README.md similarity index 99% rename from beps/0009-audit-log/README.md rename to beps/00010-audit-log/README.md index 8f1955f72f..4c08daa2ac 100644 --- a/beps/0009-audit-log/README.md +++ b/beps/00010-audit-log/README.md @@ -47,6 +47,7 @@ This feature introduces a dedicated system for recording critical security-relat - Implement a separate audit log event stream for recording security-critical events. - Defining a specific data format for audit logs. - Ensure adherence to regulatory compliance requirements through comprehensive audit logging. +- Provide access to the `WinstonLogger` transport layer to enable exporting logs to a file. ### Non-Goals From 287b0039d93960629c712208b32b70c750d6b770 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 11 Jun 2024 10:13:49 -0500 Subject: [PATCH 007/393] change from audit log to audit event Signed-off-by: Paul Schultz --- beps/00010-audit-log/README.md | 290 ----------------------------- beps/00010-event-auditor/README.md | 220 ++++++++++++++++++++++ 2 files changed, 220 insertions(+), 290 deletions(-) delete mode 100644 beps/00010-audit-log/README.md create mode 100644 beps/00010-event-auditor/README.md diff --git a/beps/00010-audit-log/README.md b/beps/00010-audit-log/README.md deleted file mode 100644 index 4c08daa2ac..0000000000 --- a/beps/00010-audit-log/README.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -title: Audit Log -status: provisional -authors: - - '@schultzp2020' -owners: -project-areas: - - core -creation-date: 2024-06-04 ---- - -# BEP: Audit Log - -[**Discussion Issue**](https://github.com/backstage/backstage/issues/23950) - -- [BEP: Audit Log](#bep-audit-log) - - [Summary](#summary) - - [Motivation](#motivation) - - [Goals](#goals) - - [Non-Goals](#non-goals) - - [Proposal](#proposal) - - [Design Details](#design-details) - - [Winston Configuration Changes](#winston-configuration-changes) - - [Data Model for Audit Logs](#data-model-for-audit-logs) - - [Actor Details Interface](#actor-details-interface) - - [Audit Request/Response Interfaces](#audit-requestresponse-interfaces) - - [Audit Log Status Interfaces](#audit-log-status-interfaces) - - [AuditLogger Interface](#auditlogger-interface) - - [Release Plan](#release-plan) - - [Dependencies](#dependencies) - - [Alternatives](#alternatives) - - [Create a Separate Winston Logger Instance](#create-a-separate-winston-logger-instance) - -## Summary - -This feature introduces a dedicated system for recording critical security-related actions and events within Backstage. By maintaining a distinct audit log stream, organizations benefit from enhanced security, improved regulatory compliance, efficient incident response, and ensured data integrity. - -## Motivation - -- Strengthen security by tracking user authentication, authorization, data access, and configuration changes. -- Facilitate adherence to regulatory requirements through logging security-sensitive operations. -- Enable efficient forensic analysis and investigation of security incidents. -- Uphold the integrity of critical audit data by implementing robust access controls and tamper-proof measures. - -### Goals - -- Implement a separate audit log event stream for recording security-critical events. -- Defining a specific data format for audit logs. -- Ensure adherence to regulatory compliance requirements through comprehensive audit logging. -- Provide access to the `WinstonLogger` transport layer to enable exporting logs to a file. - -### Non-Goals - -- Implementing mechanisms for log storage, analysis, or visualization. -- Addressing security aspects of log storage and access control beyond initial separation from regular logs. - -Both of these non-goals can be implemented as separate plugins. - -## Proposal - -The proposal introduces two key changes to implement a separate audit log event stream in Backstage. First, we recommend modifying Backstage's Winston configuration to create a distinct channel specifically for audit logs. This can be achieved by extending the [existing configuration file](https://github.com/backstage/backstage/blob/master/packages/backend-app-api/src/logging/WinstonLogger.ts). A reference implementation for such a configuration can be found [here](https://github.com/janus-idp/backstage-showcase/blob/main/packages/backend/src/logger/customLogger.ts). Separating the configuration ensures clear distinction between regular application logs and critical security events. - -Secondly, the proposal suggests creating a new shared package within the Backstage ecosystem. This package would define a standardized data format for audit logs. The format would include mandatory fields relevant for regulatory compliance and security investigations, such as actor, IP address, timestamp, and event details. The package would also provide helper functions to simplify logging audit events throughout the Backstage application. A reference implementation for such a package can be found [here](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/audit-log-node). This standardized format would streamline analysis and investigation of security-related events. - -Overall, this approach offers several benefits. By separating the configuration, security-critical events are clearly distinguished for improved monitoring and analysis. The standardized data format within the shared package would ensure consistency and facilitate compliance with regulations. Finally, the helper functions within the package would simplify the process of logging audit events. - -## Design Details - -### Winston Configuration Changes - -The proposal involves modifying Backstage's logging configuration using the Winston library. This modification creates a separate channel specifically for audit logs. - -```ts -/** - * A default formatting function is defined. This function adds timestamps, error details, and other relevant information to all logs. - */ -const defaultFormat = winston.format.combine( - winston.format.timestamp({ - format: 'YYYY-MM-DD HH:mm:ss', - }), - winston.format.errors({ stack: true }), - winston.format.splat(), -); - -const auditLogFormat = winston.format((info, opts) => { - const { isAuditLog, ...newInfo } = info; - - if (isAuditLog) { - /** - * If the flag `isAuditLog` is present and set to true, the entire message is included in the audit log format. - */ - return opts.isAuditLog ? info : false; - } - - /** - * If the flag `isAuditLog` is absent or set to false, the message is excluded from the audit log but included in the regular application log. - */ - return !opts.isAuditLog ? newInfo : false; -}); - -/** - * Two separate transport configurations are created: one for regular logs and one for audit logs. JSON formatting was chosen for easier parsing by logging tools. - */ -const transports = { - log: [ - new winston.transports.Console({ - format: winston.format.combine( - auditLogFormat({ isAuditLog: false }), - defaultFormat, - winston.format.json(), - ), - }), - ], - auditLog: [ - new winston.transports.Console({ - format: winston.format.combine( - auditLogFormat({ isAuditLog: true }), - defaultFormat, - winston.format.json(), - ), - }), - ], -}; - -const logger = WinstonLogger.create({ - meta: { - service: 'backstage', - }, - level: process.env.LOG_LEVEL ?? 'info', - format: winston.format.combine(defaultFormat, winston.format.json()), - transports: [...transports.log, ...transports.auditLog], -}); -``` - -### Data Model for Audit Logs - -To ensure consistency and facilitate regulatory compliance, the proposal suggests creating a shared package that defines a data model for audit logs. This model consists of several key components. - -#### Actor Details Interface - -This interface defines the information related to the actor who triggered the logged event. It includes fields like actor ID, IP address, hostname, and user agent. - -```ts -export type ActorDetails = { - actorId: string | null; - ip?: string; - hostname?: string; - userAgent?: string; -}; -``` - -#### Audit Request/Response Interfaces - -These interfaces define the structure of request and response data that might be included in the audit log. It's important to note that these interfaces exclude sensitive information like tokens from headers or other irrelevant details to avoid security risks. - -```ts -export type AuditRequest = { - body: any; - url: string; - method: string; - params?: any; - query?: any; -}; - -export type AuditResponse = { - status: number; - body?: any; -}; -``` - -#### Audit Log Status Interfaces - -These interfaces define the possible statuses for an audit log entry. There are three options: - -```ts -/** - * Indicates the event was successful. - */ -export type AuditLogSuccessStatus = { status: 'succeeded' }; - -/** - * Indicates the event failed and includes details about the encountered errors. - */ -export type AuditLogFailureStatus = { - status: 'failed'; - errors: ErrorLike[]; -}; - -/** - * Indicates the event failed with errors that don't perfectly fit the expected structure. - */ -export type AuditLogUnknownFailureStatus = { - status: 'failed'; - errors: unknown[]; -}; - -export type AuditLogStatus = AuditLogSuccessStatus | AuditLogFailureStatus; -``` - -#### AuditLogger Interface - -This interface defines the functionalities of an `AuditLogger` class. This class provides methods for: -- Extracting the actor ID from an Express request (if available). -- Creating detailed audit log information based on provided options. -- Logging an audit event with a specific level (info, debug, warn, or error). - -```ts -/** - * Common fields of an audit log. Note: timestamp and pluginId are automatically added at log creation. - * - * @public - */ -export type AuditLogDetails = { - actor: ActorDetails; - eventName: string; - stage: string; - request?: AuditRequest; - response?: AuditResponse; - meta: JsonValue; - isAuditLog: true; -} & AuditLogStatus; - -export type AuditLogDetailsOptions = { - eventName: string; - stage: string; - metadata?: JsonValue; - response?: AuditResponse; - actorId?: string; - request?: Request; -} & (AuditLogSuccessStatus | AuditLogUnknownFailureStatus); - -export type AuditLogOptions = { - eventName: string; - message: string; - stage: string; - level?: 'info' | 'debug' | 'warn' | 'error'; - actorId?: string; - metadata?: JsonValue; - response?: AuditResponse; - request?: Request; -} & (AuditLogSuccessStatus | AuditLogUnknownFailureStatus); - -export type AuditLoggerOptions = { - logger: LoggerService; - authService: AuthService; - httpAuthService: HttpAuthService; -}; - -export interface AuditLogger { - /** - * Processes an express request and obtains the actorId from it. Returns undefined if actorId is not obtainable. - * - * @public - */ - getActorId(request?: Request): Promise; - - /** - * Generates the audit log details to place in the metadata argument of the logger - * - * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object - * @public - */ - createAuditLogDetails( - options: AuditLogDetailsOptions, - ): Promise; - - /** - * Generates an Audit Log and logs it at the level passed by the user. - * Supports `info`, `debug`, `warn` or `error` level. Defaults to `info` if no level is passed. - * - * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object - * @public - */ - auditLog(options: AuditLogOptions): Promise; -} -``` - -## Release Plan - -The release plan involves initially creating a shared audit log package. Following this, the audit log will be implemented in core packages and other plugins. The first targets should be high-priority areas, such as the scaffolder and catalog systems. Since adding the audit log will not disrupt existing functionality, the release plan is simplified. - -## Dependencies - -- `@backstage/backend-app-api` - -## Alternatives - -### Create a Separate Winston Logger Instance - -We could create a separate instance of the Winston logger. However, this approach would necessitate core packages and plugins to include an additional argument in their constructor. diff --git a/beps/00010-event-auditor/README.md b/beps/00010-event-auditor/README.md new file mode 100644 index 0000000000..d94e421433 --- /dev/null +++ b/beps/00010-event-auditor/README.md @@ -0,0 +1,220 @@ +--- +title: Event Auditor +status: provisional +authors: + - '@schultzp2020' +owners: +project-areas: + - core +creation-date: 2024-06-04 +--- + +# BEP: Event Auditor + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/23950) + +- [BEP: Event Auditor](#bep-event-auditor) + - [Summary](#summary) + - [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Proposal](#proposal) + - [Design Details](#design-details) + - [Data Model for Audit Events](#data-model-for-audit-events) + - [Actor Details Interface](#actor-details-interface) + - [Audit Request/Response Interfaces](#audit-requestresponse-interfaces) + - [Audit Event Status Interfaces](#audit-event-status-interfaces) + - [EventAuditor Interface](#eventauditor-interface) + - [Release Plan](#release-plan) + - [Dependencies](#dependencies) + - [Alternatives](#alternatives) + +## Summary + +This feature introduces a dedicated system for recording critical security-related actions and events within Backstage. By maintaining a distinct audit event stream, organizations benefit from enhanced security, improved regulatory compliance, efficient incident response, and ensured data integrity. + +## Motivation + +- Strengthen security by tracking user authentication, authorization, data access, and configuration changes. +- Facilitate adherence to regulatory requirements through logging security-sensitive operations. +- Enable efficient forensic analysis and investigation of security incidents. +- Uphold the integrity of critical audit data by implementing robust access controls and tamper-proof measures. + +### Goals + +- Develop a backend service for the audit event stream to record security-critical events. +- Ensure compliance with regulatory requirements through detailed audit event logging. + - Refer to [NIST: Audit and Accountability](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU) +- Establish a standardized data format for audit events. + - Refer to [NIST: Content of Audit Records](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU-03) +- Provide access to the transport layer for customizable output options. + +### Non-Goals + +- Implementing mechanisms for event storage, analysis, or visualization. +- Addressing security aspects of event storage and access control beyond initial separation from regular events. + +Both of these non-goals can be implemented as separate plugins. + +## Proposal + +The proposal introduces a crucial change to implement a dedicated audit event stream in Backstage. We recommend creating a new backend service using Winston to establish a distinct channel specifically for audit events. This service would act as a wrapper around Winston, providing methods with strict interfaces to ensure uniformity across audit events throughout the Backstage application. By separating the configuration, we can clearly distinguish between regular application events and critical security events. The event format would include mandatory fields relevant to regulatory compliance and security investigations, such as actor, IP address, timestamp, and event details. This standardized format would streamline the analysis and investigation of security-related events. + +Overall, this approach offers several benefits. Separating the configuration allows for the clear distinction of security-critical events, enhancing monitoring and analysis. The standardized data format within the service ensures consistency and facilitates compliance with regulations. Finally, the service methods simplify the process of logging audit events. + +## Design Details + +### Data Model for Audit Events + +To ensure consistency and facilitate regulatory compliance, the proposal suggests creating a shared package that defines a data model for audit events. This model consists of several key components. + +#### Actor Details Interface + +This interface defines the information related to the actor who triggered the logged event. It includes fields like actor ID, IP address, hostname, and user agent. + +```ts +export type ActorDetails = { + actorId: string | null; + ip?: string; + hostname?: string; + userAgent?: string; +}; +``` + +#### Audit Request/Response Interfaces + +These interfaces define the structure of request and response data that might be included in the audit event. It's important to note that these interfaces exclude sensitive information like tokens from headers or other irrelevant details to avoid security risks. + +```ts +export type AuditRequest = { + body: any; + url: string; + method: string; + params?: any; + query?: any; +}; + +export type AuditResponse = { + status: number; + body?: any; +}; +``` + +#### Audit Event Status Interfaces + +These interfaces define the possible statuses for an audit event entry. There are three options: + +```ts +/** + * Indicates the event was successful. + */ +export type AuditEventSuccessStatus = { status: 'succeeded' }; + +/** + * Indicates the event failed and includes details about the encountered errors. + */ +export type AuditEventFailureStatus = { + status: 'failed'; + errors: ErrorLike[]; +}; + +/** + * Indicates the event failed with errors that don't perfectly fit the expected structure. + */ +export type AuditEventUnknownFailureStatus = { + status: 'failed'; + errors: unknown[]; +}; + +export type AuditEventStatus = AuditEventSuccessStatus | AuditEventFailureStatus; +``` + +#### EventAuditor Interface + +This interface defines the functionalities of an `EventAuditor` class. This class provides methods for: +- Extracting the actor ID from an Express request (if available). +- Creating detailed audit event information based on provided options. +- Logging an audit event with a specific level (info, debug, warn, or error). + +```ts +/** + * Common fields of an audit event. Note: timestamp and pluginId are automatically added at event creation. + * + * @public + */ +export type AuditEventDetails = { + actor: ActorDetails; + eventName: string; + stage: string; + request?: AuditRequest; + response?: AuditResponse; + meta: JsonValue; + isAuditEvent: true; +} & AuditEventStatus; + +export type AuditEventDetailsOptions = { + eventName: string; + stage: string; + metadata?: JsonValue; + response?: AuditResponse; + actorId?: string; + request?: Request; +} & (AuditEventSuccessStatus | AuditEventUnknownFailureStatus); + +export type AuditEventOptions = { + eventName: string; + message: string; + stage: string; + level?: 'info' | 'debug' | 'warn' | 'error'; + actorId?: string; + metadata?: JsonValue; + response?: AuditResponse; + request?: Request; +} & (AuditEventSuccessStatus | AuditEventUnknownFailureStatus); + +export type EventAuditorOptions = { + logger: LoggerService; + authService: AuthService; + httpAuthService: HttpAuthService; +}; + +export interface EventAuditor { + /** + * Processes an express request and obtains the actorId from it. Returns undefined if actorId is not obtainable. + * + * @public + */ + getActorId(request?: Request): Promise; + + /** + * Generates the audit event details to place in the metadata argument of the logger + * + * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object + * @public + */ + createAuditEventDetails( + options: AuditEventDetailsOptions, + ): Promise; + + /** + * Generates an Audit Event and logs it at the level passed by the user. + * Supports `info`, `debug`, `warn` or `error` level. Defaults to `info` if no level is passed. + * + * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object + * @public + */ + auditEvent(options: AuditEventOptions): Promise; +} +``` + +## Release Plan + +The release plan involves initially creating a shared audit event package. Following this, the audit event will be implemented in core packages and other plugins. The first targets should be high-priority areas, such as the scaffolder and catalog systems. Since adding the audit event will not disrupt existing functionality, the release plan is simplified. + +## Dependencies + +- `@backstage/types` + +## Alternatives + +N/A From ebfeb40305ae72814d62ed01d5d27b06715c4798 Mon Sep 17 00:00:00 2001 From: Simon Stamm Date: Wed, 19 Jun 2024 17:53:00 +0200 Subject: [PATCH 008/393] feat(apidocs): add resolvers prop to AsyncApiDefinitionWidget Signed-off-by: Simon Stamm --- .changeset/plenty-mails-do.md | 5 ++ plugins/api-docs/README-alpha.md | 56 +++++++++++++++++ plugins/api-docs/README.md | 63 +++++++++++++++++-- plugins/api-docs/api-report.md | 9 +++ .../AsyncApiDefinition.tsx | 16 ++++- .../AsyncApiDefinitionWidget.tsx | 9 +++ .../AsyncApiDefinitionWidget/index.ts | 5 +- 7 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 .changeset/plenty-mails-do.md diff --git a/.changeset/plenty-mails-do.md b/.changeset/plenty-mails-do.md new file mode 100644 index 0000000000..6a9e815ebb --- /dev/null +++ b/.changeset/plenty-mails-do.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Added `resolvers` prop to `AsyncApiDefinitionWidget`. This allows to override the default http/https resolvers, for example to add authentication to requests to internal schema registries. diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index 676a2c66c1..eb84ef8c27 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -50,6 +50,7 @@ To link that a component provides or consumes an API, see the [`providesApis`](h - [Custom Api Renderings](#custom-api-renderings) - [Adding Swagger UI Interceptor](#adding-requestinterceptor-to-swagger-ui) - [Providing Swagger UI Specific Supported Methods](#provide-specific-supported-methods-to-swagger-ui) + - [Custom Resolvers for AsyncApi](#custom-resolvers-for-asyncapi) - [Integrations](#integrations) - [Implementing OAuth 2 Authorization Code flow with Swagger UI](#implementing-oauth-2-authorization-code-flow-with-swagger-ui) @@ -1092,6 +1093,61 @@ export default createExtensionOverrides({ N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to the `supportedSubmitMethods` parameter. +##### Custom Resolvers for AsyncApi + +You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the `resolvers` prop to the `AsyncApiDefinitionWidget`. This is an example: + +```tsx +... +import { + AsyncApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const myCustomResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(request, { + headers: { + X-Custom: 'Custom', + }, + }); + return response.text(); + }, + }; + + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'asyncapi') { + return { + ...obj, + component: (definition) => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + } + }) +] +``` + ### Integrations #### Implementing OAuth 2 Authorization Code flow with Swagger UI diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index 2387752cc9..2738cc09ae 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -222,10 +222,6 @@ security: - [read_pets, write_pets] ``` -## Links - -- [The Backstage homepage](https://backstage.io) - ### Adding `requestInterceptor` to Swagger UI To configure a [`requestInterceptor` for Swagger UI](https://github.com/swagger-api/swagger-ui/tree/master/flavors/swagger-ui-react#requestinterceptor-proptypesfunc) you'll need to add the following to your `api.tsx`: @@ -319,3 +315,62 @@ export const apis: AnyApiFactory[] = [ N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to the `supportedSubmitMethods` parameter. + +### Custom Resolvers for AsyncApi + +You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the `resolvers` prop to the `AsyncApiDefinitionWidget`. This is an example: + +```tsx +... +import { + AsyncApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const myCustomResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(request, { + headers: { + X-Custom: 'Custom', + }, + }); + return response.text(); + }, + }; + + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'asyncapi') { + return { + ...obj, + component: (definition) => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + } + }) +] +``` + +## Links + +- [The Backstage homepage](https://backstage.io) diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 2fc852777b..504a11151b 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -83,6 +83,15 @@ export const AsyncApiDefinitionWidget: ( // @public (undocumented) export type AsyncApiDefinitionWidgetProps = { definition: string; + resolvers?: AsyncApiResolver[]; +}; + +// @public (undocumented) +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; }; // @public (undocumented) diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx index 85ce15132a..92052d9f6a 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -19,6 +19,7 @@ import '@asyncapi/react-component/styles/default.css'; import { makeStyles, alpha, darken } from '@material-ui/core/styles'; import React from 'react'; import { useTheme } from '@material-ui/core/styles'; +import { AsyncApiResolver } from './AsyncApiDefinitionWidget'; const useStyles = makeStyles(theme => ({ root: { @@ -143,7 +144,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const httpsFetchResolver = { +const httpsFetchResolver: AsyncApiResolver = { schema: 'https', order: 1, canRead: true, @@ -153,7 +154,7 @@ const httpsFetchResolver = { }, }; -const httpFetchResolver = { +const httpFetchResolver: AsyncApiResolver = { schema: 'http', order: 1, canRead: true, @@ -175,15 +176,24 @@ const config = { type Props = { definition: string; + resolvers?: AsyncApiResolver[]; }; -export const AsyncApiDefinition = ({ definition }: Props): JSX.Element => { +export const AsyncApiDefinition = ({ + definition, + resolvers, +}: Props): JSX.Element => { const classes = useStyles(); const theme = useTheme(); const classNames = `${classes.root} ${ theme.palette.type === 'dark' ? classes.dark : '' }`; + // Overwrite default resolvers if custom ones are set + if (resolvers) { + config.parserOptions.__unstable.resolver.resolvers = resolvers; + } + return (
diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx index 2e8710b008..7089143610 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx @@ -25,9 +25,18 @@ const LazyAsyncApiDefinition = React.lazy(() => })), ); +/** @public */ +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; +}; + /** @public */ export type AsyncApiDefinitionWidgetProps = { definition: string; + resolvers?: AsyncApiResolver[]; }; /** @public */ diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts index 7cc49e3138..116eb81295 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts @@ -15,4 +15,7 @@ */ export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; -export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget'; +export type { + AsyncApiDefinitionWidgetProps, + AsyncApiResolver, +} from './AsyncApiDefinitionWidget'; From 7f779c705f52b6c18ae5d7b5714f46254f7baa42 Mon Sep 17 00:00:00 2001 From: mario ma Date: Tue, 25 Jun 2024 16:55:55 +0800 Subject: [PATCH 009/393] fix: change externalAccess to optional Signed-off-by: mario ma --- .changeset/mighty-days-kiss.md | 6 ++++++ packages/backend-app-api/config.d.ts | 2 +- packages/backend-defaults/config.d.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/mighty-days-kiss.md diff --git a/.changeset/mighty-days-kiss.md b/.changeset/mighty-days-kiss.md new file mode 100644 index 0000000000..11b8c1b149 --- /dev/null +++ b/.changeset/mighty-days-kiss.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/backend-app-api': patch +--- + +change externalAccess to optional diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index a23bca447b..25a5baac86 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -71,7 +71,7 @@ export interface Config { * the Backstage ecosystem to get authorized for access to APIs that do * not permit unauthorized access. */ - externalAccess: Array< + externalAccess?: Array< | { /** * This is the legacy service-to-service access method, where a set diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index ab1e443fc2..d01aca979d 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -74,7 +74,7 @@ export interface Config { * the Backstage ecosystem to get authorized for access to APIs that do * not permit unauthorized access. */ - externalAccess: Array< + externalAccess?: Array< | { /** * This is the legacy service-to-service access method, where a set From bbacf63e67f5ce044eae395ae535551c55015d73 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 25 Jun 2024 09:09:49 -0500 Subject: [PATCH 010/393] add non-repudiation Signed-off-by: Paul Schultz --- beps/00010-event-auditor/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/beps/00010-event-auditor/README.md b/beps/00010-event-auditor/README.md index d94e421433..5f4e767806 100644 --- a/beps/00010-event-auditor/README.md +++ b/beps/00010-event-auditor/README.md @@ -45,6 +45,7 @@ This feature introduces a dedicated system for recording critical security-relat - Develop a backend service for the audit event stream to record security-critical events. - Ensure compliance with regulatory requirements through detailed audit event logging. - Refer to [NIST: Audit and Accountability](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU) + - Refer to [NIST: Non-Repudiation](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU-10) - Establish a standardized data format for audit events. - Refer to [NIST: Content of Audit Records](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU-03) - Provide access to the transport layer for customizable output options. From 5177f6f0e1ce1b453f0975eea0c0a49e6c3783c2 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 31 May 2024 12:35:40 +0200 Subject: [PATCH 011/393] User documentation for the Notifications and Signals Signed-off-by: Marek Libra --- docs/notifications/getting-started.md | 168 +++++++++++++++++++++++ docs/notifications/notificationsPage.png | Bin 0 -> 137787 bytes 2 files changed, 168 insertions(+) create mode 100644 docs/notifications/getting-started.md create mode 100644 docs/notifications/notificationsPage.png diff --git a/docs/notifications/getting-started.md b/docs/notifications/getting-started.md new file mode 100644 index 0000000000..6a7ea6314e --- /dev/null +++ b/docs/notifications/getting-started.md @@ -0,0 +1,168 @@ +--- +id: getting-started +title: Getting Started +description: How to get started with the notifications and signals +--- + +The Backstage Notifications System provides a way for plugins and external services to send notifications to Backstage users. +These notifications are displayed in the dedicated page of the Backstage frontend UI or by frontend plugins per specific scenarios. +Additionally, plugins can implement processors to send notifications through external channels like email, Slack, or MS Teams. + +Notifications can be optionally integrated with the signals (a push mechanism) to ensure users receive them immediately. + +### Upgrade to the latest version of Backstage + +To ensure your version of Backstage has all the latest notifications and signals related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! + +## About notifications + +Notifications are messages sent to either individual users or groups. +They are not intended for inter-process communication of any kind. + +There are two basic types of notifications: + +- **Broadcast**: Messages sent to all users of Backstage. +- **Entity**: Messages delivered to specific listed entities, such as Users or Groups. + +Example of use-cases: + +- System-wide announcements or alerts +- Notifications for component owners: e.g., build failures, successful deployments, new vulnerabilities +- Notifications for individuals: e.g., updates you have subscribed to, new required training courses +- Notifications pertaining to a particular entity in the catalog: A notification might apply to an entity and the owning team. + +## Configuration + +### Notifications Backend + +The Notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters. + +The plugin uses a relational [database](https://backstage.io/docs/getting-started/config/database) for persistence, no specifics are introduced in this context. + +No additional configuration in the app-config is needed, except for optional additional modules for `processors`. + +### Notifications Frontend + +The recipients of notifications have to be entities in the catalog, e.g. of the User or Group kind. + +Otherwise no specific configuration is needed for the front-end notifications plugin. + +All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end. + +![Notifications Page](notificationsPage.png) + +## Use + +New notifications can be sent either by a backend plugin or an external service through the REST API. + +### Backend + +Regardless of technical feasibility, a backend plugin should avoid directly accessing the notifications REST API. +Instead, it should integrate with the `@backstage/plugin-notifications-node` to `send` (create) a new notification. + +The reasons for this approach include the propagation of authorization in the API request and improved maintenance and backward compatibility in the future. + +```ts +import { notificationService } from '@backstage/plugin-notifications-node'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'myPlugin', + register(env) { + env.registerInit({ + deps: { + // ... + notificationService: notificationService, + }, + async init({ config, logger, httpRouter, notificationService }) { + httpRouter.use( + await createRouter({ + // ... + notificationService, + }), + ); + }, + }); + }, +}); +``` + +To emit a new notification: + +```ts +notificationService.send({ + recipients /* of the broadcast or entity type */, + payload /* actual message */, +}); +``` + +Refer the [API documentation](https://github.com/backstage/backstage/blob/master/plugins/notifications-node/api-report.md) for further details. + +### Signals + +The use of signals with notifications is optional but generally enhances user experience and performance. + +When a notification is created, a new signal is emitted to a general-purpose message bus to announce it to subscribed listeners. + +The frontend maintains a persistent connection (web socket) to receive these announcements from the notifications channel. +The specific details of the updated or created notification should be retrieved via a request to the notifications API, except for new notifications, where the payload is included in the signal for performance reasons. + +In a frontend plugin, to subscribe for notifications' signals: + +```ts +import { useSignal } from '@backstage/plugin-signals-react'; + +const { lastSignal } = useSignal('notifications'); + +React.useEffect(() => { + /* ... */ +}, [lastSignal, notificationsApi]); +``` + +### Consuming Notifications + +In a front-end plugin, the simplest way to query a notification is by its ID: + +```ts +import { useApi } from '@backstage/core-plugin-api'; +import { notificationsApiRef } from '@backstage/plugin-notifications'; + +const notificationsApi = useApi(notificationsApiRef); + +notificationsApi.getNotification(yourId); + +// or with connection to signals: +notificationsApi.getNotification(lastSignal.notification_id); +``` + +### External Services + +When the emitter of a notification is a Backstage backend plugin, it is mandatory to use the integration via `@backstage/plugin-notifications-node` as described above. + +If the emitter is a service external to Backstage, an HTTP POST request can be issued directly to the API, assuming that authentication is properly configured. +Refer to the service-to-service auth documentation for more details, focusing on the Static Tokens section for the simplest setup option. + +An example request for creating a broadcast notification might look like: + +```bash +curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_BASE64_SHARED_KEY_TOKEN" -d '{"recipients":{"type":"broadcast"},"payload": {"title": "Title of broadcast message","link": "http://foo.com/bar","severity": "high","topic": "The topic"}}' +``` + +## Additional info + +Up-to-date documentation can be found in the plugins' README files: + +- https://github.com/backstage/backstage/blob/master/plugins/notifications/README.md + +- https://github.com/backstage/backstage/blob/master/plugins/notifications-backend/README.md + +- https://github.com/backstage/backstage/blob/master/plugins/notifications-node/README.md + +- https://github.com/backstage/backstage/blob/master/plugins/signals-react/README.md + +## Processors + +The default backend behavior when emitting a new notification can be tweaked using processors. + +Depending on the needs, a processor can modify the content of a notification or route it to different systems like email, Slack, or other services. + +A good example of how to write a processor is the [Email Processor](https://github.com/backstage/backstage/tree/master/plugins/notifications-backend-module-email). diff --git a/docs/notifications/notificationsPage.png b/docs/notifications/notificationsPage.png new file mode 100644 index 0000000000000000000000000000000000000000..7c96ab77af2d2d9527acefa99027468e4d5b3782 GIT binary patch literal 137787 zcmafaRZtvVxGnB7gJ*D;1Rvbp-3bybxI2Tp4HgK5;O_2D@C0`anm};3K`#F}_deh1 z+7JCz^-Fj4+RJu~x~d#D1_%QV4h~yEURo0l4mA)C4zULf!8c7OFwi~Xcj=e63A&5Cfjb-=CjNg59xgMB zuDmJ*1o+9j%-^;H2EF%si|tsl07HY^dZERMhoC zaERgHsNfW&CA583kGmn`j%zxUyu+(7*yL5th{%4=S@Rh(!f=9bkvNqKMoAe!om4|o zAKC*5#caN#Fc%CmBbj=+Y;Bre>tWO{xx<_^*Nuwi?k3*COD30$OwIIt4DS1L&rt? z{??&Z=`jyFiZm1WH2uM85*H4lc0uMZ+~umOn%bN=crL}9y(_WkN07+aV;Wd%hnxMd zyPIt~n$TT2{PBBlkpy1Qv21cmfFjQ_PgP~aSwu0Lz}nNXW;=QPc4&OxOcS^FxSq>r9#prj^4~@>*y`2 zqvTpX_8gnt0v|Clr>?YK_YsYRRx$RQmmSQK-=-o7ys=_s+*IXa&O$G^A`OvI6vq8h ziB)NL=O;2&e1Lp{%qTgC2~qD7jxA~8w5&m|>{Hp~TI9qO3E$Uqe z=W-MJT5)1R9S{hDeE3`F5bDZvJVns^1y8NvZhD8dXOiZ%?X2zV>^ownBY{VM&&`Yk zw|M7xA7gA6yH0R+;oeB%TnOBE>^=^<#q9gl)f{Ir67P$h;8j2SP4tyqo7bNv7|v!j{5wlkaJGkK$^*n= z$bQw_vdxjZ))UjC8}p#N$ET;8Z@nIK6b>fy=k8Be@Ebl{+^S8asG}u?eXW$x_IVR- zm}lnm?n~O4lI`XS;?@?UPsLMuOZJ~lv!q<*#4jmooJ+<|-Go*>++I^nOUG9g7wT7t zYhp#IM9 zy#0=doq=0pxsxYetm=sD(=)8hGuthK8uy-dfq4q?%IKt6G()_av%g=0YctpgiZCXM z7$ZjpjqaAi$n{GwiRZOk6PGAGdtzWd^Tv$cnDXCbgU7Sc1}=L1-LVsU9=aYmQ%Ri7 zQP*mk0m4DrBp*{}Xb)wulASMbR{e*WT0Jbj1l|tae;3t=Y(F zLOIDJljJuOzeA9$v=~7;kXkyNXM7@<(6`Zs;oKA`y+J21Vaj3ZP+Z8S3f@Tgx1)u! zQ&;PmvH%QsC$HjBubtwK-Q<=~ka`tS zs&CARPY=V2&XmOt*haZ(QA+g~;zQn=g%TS1dLZjjd~Ch7yz?&Z+qq8wSK1SE=Uiv;e;FH$w;D zKe?m0obmQ&tKm-%pI4dXqYdmE=fY)WQEK8I5I&aJ_WKBHB1?^vF9CP2@H?OjRj>T6 zJwK$M5=d$jwSoBV`*$6HGUwC-*f7eMdB0m0_@|jLe{A++gFD}@n=5?g&E_V!2~$=( zs_CiLo2kHo;YritTO~GG5)^gPao?xt7|$9W2e4SiJo@`%hH;#{ za@E!?Jw74JljZOi8y6(Z?(>Rpitp5I*tYQp+q{&$NaiesWC?zNx6JQE+mrjpeBw#CqVVVweut#yRLv?B}=6G?#HrX2_he7;-{`C<5eeY1A5c zCG!UI|H2Ov;pPH6t?J_hPW-6ip+Mp1;7>l2SPZU24ZEeJ@dO}04 z4-4SPqc?Z>1fkL8zFpW5#>K*zB&q%qaB)0vVV(g!KP#Uh z6jM*vWfyAS_OOng)hRq5XwMSQY7fX-EDh0T>le1YpjNY-a)kunvo^q0Qfa4MP=5Eh zzUkSBa_V2+VDEfY#gUSFhdI6DL=k=Xv5$-@w5+yJs zEQK1wOfr_Z&^0msDI|W%w~OfttBha}h`v~gcBF+w2H+)Syq7qL(r)lA2lSGoyEo58 z-r&qsOO;6F-iqwWrmaVBJcLw}MzLC?1X z4_lLpZTMu_NfLrV&J`#uz9z0D$(94?6}N?xlu0v7ATG}sA33gsRGOy`y|_F65&Ee5 z`rI>RUSWJC%omoP6hpWEhE1L4QF$B?%S-<&e|ak9BiykM1pne4WD?i~cH=P+GDb?l zu(e3e$k!g*cc0m6GhwnqwK(Ad^6JXo(Da)WKW^AI?R} z;}V-h@KE%hf%t$1!14-Oo(3D@apHJ04sX+vHL?-Cv3BBf#L38}KLy)w{Ufvlf>8FD zPxN(|3K%rzX9249js~*Sq-Tug5m#g%ehi4wTbe91L39UKa*%JkkFF$Xe8kZ9#{`E@~{AQ2TlD!jgbvQne++DUQx zq9l?z&sXlmsBM?aMB5NI9LHDmmY@mZo#*(0r>-JKBb?w{C{r+GrYG}RcR%s0)I1L{ ztSz!B!g@&L-`y{s0!s^0LbI;$%=MUbBZ!*kZnWK55jfZZsSI%S;)B2Uj&J_CYzQ?& z3YG~giiz?FFK*6d*qg{fC7>a%*Q;IXsN;GxM!;G^R7GFiH;$jW)-AGvsw5*)PR1*Ypg_*8X|xUdT`W zCn@blknfT;12KgR;ll74x~Lz<{l`sF!7=(>I>18Sy(F-z!UW(!v${KMCrniUwk#31 z5`Ej`OQI=84DMQ3o0|TU=DBgfQxTe)OFe;Z(!RCS28(l-0ii|W|C+xhKinH7% zE_AkFzUjS;VfIP3{^e+?moCN>@JIS(gaGzMF*Tv_-_-&-wlZ0N!?^F1Ex)qOJQ4uJW`M7n#CF+Utyq;)q0yl`j;2ll?V&cS z-%Y>D!IY2Au>dONtjYvl*RIetjQWD-TBh4UW_|)I|0ZPPvtCZlaSSCqAQsU4yCl@) zp+_S{wEE;dFuq_njrhYoL>3L!0RP3UOTr1Tw+BB2jd3tQd(;KXtFTzv^}@n1F;xc1K3dZpP0k@H)oB2e4r3;@ z@Mo!|asxRf?uC-hE(WsxCcXTo!(4<8$)6aHo%y=t;o5jqLKc>Ub49|%(JHB-Xu%8L zBdu_IwVLg}jg05h6AQY(l%A!Qk$T|P9Z4W|3K$X4u)C<)_v}yj@7Gha^9V8x3HD&^%smHZY;dRz z9{Ub;GH4hj6R7x|+ge0&LN_!=L@&ePFF)RQKo`NNJ+0geZ^$$+CcncW{ICs*D$U!5#kMUYq(n(8EVmDh9g_eLT!J6g#w zvA*gTIzd-qbZK$|m?$R-*3G?pNr*kqHSvZmwG0vHAmKGC)$eY#@ZIOYS@!K@xusd^KhZG zpaD(L%+C=`($TgM0m|IHwX+46(#m4tiQPjalpeNWRO!__o> ztsN>JEH5RDo$nSgz}2~z0M3XQwmt6#yc}ZWJndjIR~rz|91&s=(J0P=(%HC}qT)@d zTvybs{~=#xbt}vxzV9r82C?s`P*OhUsnZh)LnLgs8am&VeKG9K0y>=jDMoSg>S7~l zhk|TXH2CXoIVD5L7Qi0Mab)H~XQm3it_zL`kF3R`c(k8lzydapi1skQ+g!BSXvgMl zS;Qs{t{ilbP)BpX#KR)y%ezPnc`j{Brs+rEpuiw+5lC%y@O=FO_k6n0zU}fJgU$Hk zpX0!ocjo4Mg5S^_W2El@jZKWJOONs3RbudPIH+GLzoj<34d&#a8vkV_x^?iY`p#48 z-0i~dl{rDnodm2R+Ejt?v}WJW1kFP>C-TRtt?lPH?`d_rAArQU;8-xN&xqbM&;ilCT^nF$r}62 zx^J{;grV4W?yW@QX;juwk(e++0VS?Q4kUR7={#nW{XE391NFO#>~5r=- zXP^yCCj`Bl)oUsHqdlbqWwFdIX5f)U5GbM{5@IL3un*M!3ZqJdi=!Sxxn!&O2+1VX zq%DkE%Oybiz*_p3<(Fv5SVfs3w=ua=LJ51H8w-}w++e;TCJ@pITOkj{T+*u(lnl)gy?pcHl-u&Lr)kfRoqu8W93i{03ssViQ+Agx(zZ>;5LcvM zDX-0lzNOpoZbvCgLIGrMt@dgmPQU?i!^OY*ba5)JWXe8$7M!tG$)-nqx7ryEPg5tx zQ?Oyl?9LrJf$l0&K)qP_aeE#bB8PqZtxVjP@By&s>Zcr1(s?_7!jHn~9l~+0i z&0ob&rV^WVI50{>T1bpq40V*laN(BlMR(?wJfEJ7x}5K{KmOCn^lkNTc0+H=s(m`1 zmJ+LFMFx^+y@qf36)12uW4{DBs&2-40_7ZkEWX%-8Y4xVGip|cNubG8#mxyly*fv{ z1Tnu2Rzpu7>{4WtC+HRFRevG6OeiKuX9IE&IE8D8W0LBhY@qi#-z59QJ7VqiX|u8h zP8uCkGNCxHr*)EgQoN-SQy{C@A2!X6oue6R{)3jm_w+(}X`|f*9QGM!(gkL5hDfIL z)zkPfW>*BqN7bOPU$ zXh|y}fy}F;ykm ziUn}KYWSH1kY6D{%ljn-)awC|_K=jTZoLTkO6^BmH2GVbtkbvaMWEE>wO^0AuPe-U%yUa)`iC4H(ErzMd_rAG z*g=?@>BmoTdGgdb@n1wqQb(1B*O(jzp&<0r0I9X5G~Q(k5kGPuN6Zwa z0XP>QN7KY_pP5&ez7q%blrhuY$g0siKg}1P*ke;Rqisj7I#F8you8y#YpO|mJqDGF z(y|v%hq9)=DvXu@gYCkT_nk|LrVqO8sc@yh?}ltri}zsBs7Nt`X+iQ~PI#QU&*Qo29X!SBkxyc6iy^pPs%ZR|AVu-TKlT~82c9qxSi zLCxFtEkKk^e86z-zM3?mh~eHo9t34c%agy$FPTuTjTv?>0XTAtTOMciNO`U*oG^+B`SxIKurz#zt;B1zag$b*vHz(*%UU5{T@*7&i?p)&O&}{|nyInD@yiz1*a<9eVPD3Ar=LZyU2L6i;Vuj0A+Hlx;)&=hp`*zV1oTIK zhUl*8S^YrU`Cj#zoo>_mMHoiaDZR$`VVXXvXLX-vl#?^ZXrP9$xCT2gBI8SRFDC_b zv8|DURXXBn{D%3*P3hvBrNZd)FpLo?(JIwok!eqNxZ0y?^nQu)-PEsr1&Tc$=!&Z2 z<{~^MU5Ys$LnKZK^?ROoj8GZt)3z3N-_;&nYDj(yyERENta3Xiyk#KQUR+aht#ySm z!WNdYxXc5SQMqa$IOkcml_kSBBaJeAF|twk3M)AIIAYZpX_-1xlbnaG!;XQiSO0>Z zacjYO42!gdq-aKa?f6fYX{}xPzNTv0TYb3bFtjYDso0(3f1%}4 zTDs3}=J$kd5^bB>Q+zQmES=I?gptX$#G_|sYmiD2vYO%dch6axRKw|(!d2dQ_56(N z;Og0ZBqxoZwq^{caqnwd>*5H3urgxzu~qw>C*y`4z2boV)L9st1Rs+n0Te7=4wfOf|Dk zC(Ol>*Iy<|h@#1`)5U}4Jt5KA8Sv-r-J*r(75d$=mn zA#862-`tL_=k^~-ffTjB5HJo^D#_}0ZU>$V?`&sEmk~|Pe+-#lKx_{j5SLeykL5OF z>ijO8OoP4RJ!fuAB(|chQG<9V9nBP$TRlI>QFma6Vz+R@0;gS{4Q?uE);-c0_>~Ttt z-T~|ZpTU8yzyjsOE4#rgGlCAVdbVRaiE|nPZy1^my+XYV8`{74?wqx{_W{wKR&wNu zyRGSNL=KVVUBk8g=bx6_pVhxa&D>8=m6dvjW#Dx|#5QmvgFn!_A_xerV(M3zt>?3L z_sZ5pUN;xNUO#jMWez@{eddqf`!QL2BuWHD%RjN36S{q#bPP6q9fF*X5s%H>d$x5a zg8oZe1H6p>v(e#i*yYCi{y;9ZYDSlW?y zQXr_!TxNlXkuJlylF5_TB$$9j-P%J6=%NQQkA81LXTz2Tcjn-J!2gf_!g(`bn}qh> zf5PC82rtq;TkY1~toqba8%kN@IjGv&;#nj79@$i?B|aJo=vUE+%CS{l4$L@?{F-z) zU!e=c#1Boi#bMx49FW9l&b0pR!#FWZGg$jQ`UXK|W)vW@wONDBz+4foaMzmT{Ew%spuqNip zIMJvVi)w)}7mt%fWCJIJHh)qsMT_hnL0F*T;)LpF%mqz5;B5I`5gP`)QCmYOsk3m1 zT#iu09Hvy+Ney*u+pwLnykaCzT4bdLn$*XFFxvOpLF!K^*sDyShCHyWAA<7r zVCL2;vnQFsmR=@g+$m#UQujc{_(#F43$ooYEP8LqEb8E9$M@FT#l+KKm4Sy-;SnY(ipHVJ3&uQ5N9Qn)M?!Z zzX;nz{{$LT(8|Vn$%!Nu);Mzk)?-lAoNB*EQqil{Hq+c(ApfMUs=lB|E;ck#wqKvS zOnVFTuOwc47ST*Qzmc*IGNBKaC3R8R_mbS=2w30$!U`pw)Gkb&Z z&NEb|24ft=el6^F>6iMzTwIM5RVA5~&{z}6JUVpTq1Brv?} z_~5silyARqM}*e6ed}p6+oPEGyU>|swLO9H$w3a}Mm0EGk^0E*xj{WvPLa_V!Rqoug%CSe3 z*LzICM^^inBfMpO3p-L~7{dan_!zM`12l|imn&;KEP7`TTb+N1g9%@YU{=}I% zDnmG{a3lH#RYW)b}$BDzjPoi zhK$iaBAT?PA2GIKw~MzCu|Fp)z#PEk5F|jF^sa5w>FDeJU{Y>QOYM8=JsX@7z!J$w zX-QzMOue8!6y!)9K^Chj9_VI0SD_xK7p-x~e|gRO(m=eF#4$VByxLXWOU|+Hydf*6 zzj$A{LQMuYc?!4QE%la_u7G-XA4vg|#_PDqCDf5$X-znbFTTUFGVDA^@G`?(D8Yf* z{%`zu|KcG)y!<4pZ}9p~W*7Q96aySc41Wy3DYzhYJP7x=*uGYj3a`iqR;~j-mr$;> z%~S6Ye_;to(HNpi6*$(_i4N{HS9{2armFVnJ6yZX#2TOjg$r$x&bL57WG7k6*` zvTmTV^Smi0`?LgrN!h?o-+Yzj;vA9I6~NOlyWY_Ir z+&^_$_{nNddqk@`Kc!6b4~3W1mXx!A6=_+(S|6uoNR)|lM5I5-a;K*fX;toWah`U4 zaqY7Af3dAVK?aJ@R$axoV8D$l1TXgk7qYk<7xOgBXxYkd*(xe-N9LpLy4_W zkTx8fNJ9?pDjnW10TmH>z1+&SiDJr6_I9(%NmJDAkM`?}(aH$iO@n^M)5 zpE^_ijEeqJ-6L{?mef~01(Y7gBNQg>^VF^Fonb#}5V(0F#!_wk$1h5g+r<=nB@3lY zoJzxFZAoSQPQDOzRI8@@fM(L?Pb$HL1mYMzO`uRyz+7gx0jp6Hpx@z@ph9RH0_fTz zj+~y^0|QTt&rWCq{=vy_R%MUMdr&cLf85R`wRs8+0RV4*L#;z^n39FlDUAmvtkt#- z@$E?#GiJS(o?s-Du0_rzGqS}$=^3mBI+15E*lkx-4fBw8c8@OH=C zO8ywTNP?+Fq7p}&z`(qd@AZ%feknMM1M1v-YjZgDMOf8^{hGHyl2h4=Kqz_ERkd^x z@dRZnl+^#_P*wzSdr#f^i}Z`Eycue+-Aj&LwOB=wxgA7Ba}EHs(PE&Z4N--P(YCFH z`WDAHI_$gVCPhrAk9MGF2)+c$#`DDMQJp!EYgIbNRaQGak#G}STG_ZP6N$f9aLIay z-B!{j4N@3J#%&OM&`B{2>!6@o+Dg0o=a00^)mYz~mRniv+Uy!#i298P9L!6pNXs=g z$vTwy5l-6t_P03LPhPel)3wlMoRq`vX)zwPa8W=5;*xP9sC}#ZolmXnODq{-HU!<_5&_SdN%>|a9fN# z@kZmE5eTkjQ6{c?hF4W2i5sZ0*o%3kFP^DoYaC=KT96{0FCZ2d{0rn(zEDEX-c3Md z^ARPq6}w^PJ?Coy`2g}$)aIMLX^JV^QUryXKz)u^T+vna$paRc?;@c z?(!c9tw30Uk;-m5RXKLKheNY=R}+~)XX{r7apPlZNx#fyW)1#WK@Vm%*A%&V2|jb) z0)OFXi-N!k+q=BFPhFJHKdxLZ{t7zGFXQ%8*u2R(YWeotbGp@ZVna@?DRctcpNG+IF zP#0=Xj$mtKcPj{k&LzuoNhyBQOxp#%Gf(fCw6_dmcj#4lC6lb)ksF<~a&D)PIWPSN z2UvIjkS^{;c|A)Tey3a*8%kQhuky{`^W09f|3X{J;SZ@)tNNsm-7JFLT=8YKCnV^k z`J^OQ2e3wgIMQRY8T}{{b;|>uka9H5r4CLM8p9^mtY!qyh7FqjTf8NVfiQ}eA{GRE zmt~Iz9(TbPTDeg1%_!x=4G5!;pFQf>1Q_ z)gzqaBAD963?!nczi{+V&&j}N+8}|yr8x|;GsiRI(K`+){cSu8*;?4~)v3TU+#-JM za6@sq{wULPcn@t$jC6Bqaw<9)2AM{=Re<4rFAo5;_LWN^8q zZijCNnwbrhQv9636f$p8&}^twIQ~#KU(dn$JDP+SM4?E#D222JswbLdpN!hp1lOkJ zDAF#lGB<9x>tQdRJlNLmn{c2-$vh3m``D3`DZo*R@2;x0Z%Fq zx}ggtR#zB7I}#mqmVVYxO`to>H}Oj9A)yb)&kP5JHl=5h!<%_xxNJWfN|5}EnEgPX zU&?%{S#>`@b9-~(Udf+r|0Jn0k-!AX#$wgR&i|M}=>qJ{X>nfWx8#-_jNbfe zi-bQ*fY(p@4s6iNM%v$Zy8KRU&HF=F#f7ru+L96bkSv#M^rpc8?;?0V@awv4lkRT1 zLqTuM*w5LvmL`h#6Lw=ZR?9)sUQR4Mx5A10>H=b}MG-AUIwvOdIUiRo$D2Kx!@US| z3n}<`@+Ai5=V+R2x$SK$o>#2C1BrK{Z-+;n z3qL4Mc$W057fr)SPNG-l;v?G_bzmVp>fa;3AVa^1ullxF`TWd!<@l+miRw>R?s~*Y zvoe9{ZXv6}%9_!pUL`ijutYDX`>2FV5}uXO!)?i=KRW$@{9`K2=r!oCQ-Im` zC`>qTT4ZN7CBx3kC*dWGzsMngMG_tA{0kg7jw(2l$1sHN+%ABgn+J3&3QaDWdY^1@ zGaP)_&Hv@vVxV5>Bw4D+=$Qb_!tQ9gQQIe?oFws(qQmLyG{D#Rk}K( zc@F+Bu3aV_KN^eROg$1NSjJ2ZiB=*HKJ|T;w>=|hD#*__n6Y=yob;0>Lc1QlArrJp zyZje1%~f<6sTn!#pgWBDY9ZAT2_0Z*Xa0-zJlR-&{|a6(;#1=1(kQ>*kX{lC=85CK z6zD~o;YmxF3MC~9|D=3u=C!LKd8s0$1XeFxwqfzS4mGYU%;$`FLutbm6#~mkfL#q@ zb{W{PVWtw`(vMb~xKKI@dMq_Nd=VCcu7$xjbb##dt(o2ZVmrh|au)wJ6@@EF0 zQbyS~sF26*xb{TJOIQNv*)%1rdEm0z^BFOOyVR*5!LH(C+V&7FNkLXi?KiYjS(qX^ zwzU)UbbPU@Y&@bJOgF+kY9?4-;TXUD$e5vq%d02ft@PesLIqt}>S;Ce_-pt3raoQT zB9ixYg`8@naCkB8W@XQ;<@;+IaUA9CS^RNU4mbDST&gsc;u7%Yv9}Jw33&IG1Sn}g zA}Z)VircsyUTN}v+&kM?i{Qz2>^6+))Pu4))hQ5M4TI1mL?bjaXAQzH zo>$xjmpE}uLO-?amy|SQ{dO@E)rXprk44wML*O2O3yIIv|57k>?5`!jLqc_WT`Be06vE-YeNob6weW$fU{{wcjXi9q_1UQjLL z7_eErnYuc4I34;;kch59+loJyj`un2V(i@iC z@8AGAwW|!Wj&+=8CL*FVM-m$9r$<>~e%%!(1ll+Ac+1(7(0L0TQ~9id>LdX>7G@zMpRp7%MdNKzfD@6g1@TK0MdN_l`^Zn2q3P zJy)}_)-`e;-=&~erV38!zOSSfiV6+L*?9PvKr(l!i^TIod4e>}@MEW$!>4iV#XAL| zPxhya(Y=uxF`Nhlc0e~!*d+M3jK!$>t(-0H=NbO|;i~;C=VsR|F>6a4t&El;uWE*8 zI&*5@zq=4r(lMmJeDEO0Gd5WwS~32Sn8*M&VM)?d<9|$>B0VuIA<`7HG!ryB?nXJUm+^WU7H$l^_@aEbL8{Na0?x`gdn9O~ zKLglK8R3;Xho_{l>%>>_(Fp+d2w{I`eFF+vSsTO=Apr>Imm`?#ol2M?GO`vnZqMYv z|J=;}Fqx;n08fQM8Z$~_S#_V2f>n+{hUS5w<7o_jnsLclGeCid_>o-d{L=y$HJ#ue z0yO8tki*g=z))2rL?x2N^}=71-P03AfQZ(QBaaQ#d{QF@!Kvxq1~1<72sTkkCpUr8 ze|I4M^xV3&0elVM`5{q=a|w_^XJNx+j*WSV#5(+%hu@xI8f44z2XUoZw$R{1qFdtf zc7j6!i`JUn%zZPsiBn^=lYO-Zr=Vc>Q@V&pyY|_FUuoChPh;|!2bp0MH@><*^Op?# z62Hj-_DLwp7)4nEQ3aQ7RZgT1Z88pH@5~Rcz=gvU9{_@}BdX?E`qnH(x5}DPW zPs9ioj+>89MbyW*6(=88w`uu;rvdR?z>->T^N4-0r|=M(yV{x_O(6QHiun>+(dus? zb0pt1e=XphrB;JA8IT!Gu026vF49taqC484QvCCY)sb_NcARv)t-Y5da#TDseBo@_ zMiti&ai}Go%QyI@+TzFp!}%C;0*h{A!&kpM-4(l6C$vbyA7j2QnBbvb2eDl10m)(EW zYjWtH@y8|Me~k2gYv{0Z?r_B%Cu))!7B@DDO65gJw*0QLebudf{W6R zPCNlei&KVcd$WZ{9lPKH1a^Yj_vht$us5n3z{Gm_YubjP_S}$c!u2<2Tk>i<&JJIg z3&DI$MANv~dv!PIc0Hfs<467I`-lOmKzhl*Q-a))?@bAxMqbje$(5xRO%weX1+B=9 zhDBu(lO>)&+L<{eIAHB#96TTy^aK`uKcPq&50XzUGB1gjb=CdFX8)=pE!Wp4ArPd5 zx!4-@q*A@Uw@)-y2DI{vqgLdh{#-UQjJXR!;P2+|8+dcfzrU@k^eE66)6Niz%v%dx zl67?yh%_xx&3;_0gC^wxQbTYw{pHMeQCxna4GhrFKX0z>1~|G z!MsV;C-Qp=f-M*S$}T>RDCusi*<9J-l+zWdkLF{r)FDH}^r;aNmk^MH}4ym3+yq8}UGqpzSuyjAFSQ*Cw_%8!oz^IsL z(l&9H`}=&v_1KaY>QoH^UqGhbHxJW`XgWL;VZ%lkvpVZN+5}zZMoKG_?q<6SHl~Sp zA&gu;jAysjn4B0zgZU@}h|IeaJQQ)$pPVOiBcHX}k2Ok{bIU9YdPj*SMl}wP(zSV92~qO3MQ2IQI@rjm0$I00 zsv1LT|NLC4L-z;zX0>clau~kL{AC++rVXTu=&wyOmU z_+RI139+N+^}?HVG0C_PNbkAxJ{Q1JtZ3IDR@MyjRss?{49Zk&0Cg_>Ay#>vwNvjfE)-1Vr#>8AB2F(f*nBx9J;?X#P93B;EilQJI+C_aB}A_t??m zjj}7PQx$uhOK3tazSi12#9PqXjIVCR#pAg7fi}{*6u0oC(tZ>{>bO~%+A9j8(*{v# z3ixv+1o}_{pW z#GgHCWQYaN=JfPdg^|)e@n{bJoEc-X>u8rhgh%0$M9J%5jj0aet8|36#TD-q&y*r)$>gRtThcg2|6X+$`wRkTw=~P9TPK?&4Gh75LW!*Lfc(S}oQG2pN*NYEUMqS?ew52X9<;LCYnkzC*XK`4J!HG|c zD5CeLDr=wWkw*6xqz0e=S58t8?A(zD=4s&>_SlBk7MSUTmpGSNeG>xMLtAookeQ;3 zV-dax(tR{AYdDD>ehOAVlAQteZ2MRX=83Qh5mUvh%VMLrPSIrILeJB(Ln780#(1Y& z0P-@LTa)u7_>y#z*XEVzYc_=vxIet-FN=*O6Z|!-5%}AKU0!~6oY?3km0FCj2^cSX zj`#UY6Hz*|iYbA)0-*L|5@00dOB}a>?Yc<7is)pa+JO+7!+xH?C848DX#L7EWMp zmHeSS-o$%zdA67AV6n0ZnzRAmp9?i6@L`GL5n9O3Wd-1l&cZ2!eivjnF*#UfnMj_MDWc>({8+p^Sq-2BijN0!iF1%u-xp!HEZeA zpNkaXBY6~5u2V8#c~)ziBX@H0)6*awPo|K9o!3R9^f+jia?vMD=y!^ru}wH%%&o!N znxhBdEg8Jywb;q9 zFM!L)ujRu{3i*~H>^gj2Hc)AmBCg*tD*rpU$+D|HFwaIz_5r=%-26x$F}~jFWoy%uiBQ z##fueQ~obcKZ3tl3(+rn&VOO)_@oxjM9*_!2Q?i*RIi%(`hNiDKp4M6OdW2{D2Z6i zX~ec2Tl+lMeL?tg>L9-ElGNF0t;;Ocd9Nn1iOgLSK&BmKQ{MhOZN;;$1RatNNO}q$}+|PyC9m=T#XRH)q3*^&kYjraER?@4<@LM zRCkIsWo*re+7fPiLvNFOj2toYMA{_Kq>ykRQ+VqJ33+=h^oL;VA97ZJ?_{ zl7`kox*-4nAOJ~3K~yk}n0XS2S;}Z`fTQngAm18OA` z@jg7_w-@zmNuO96*S*XEuEwui|By$RJ}`v&ebe~O<@YkreK^E#oG(9^#&5vP+@tX> zHOIRUzX4OjXhwebg9U!;SI=??-=oIw@j71cS%LUDwV5HeE&CMdiriD22m zO)Ao&qwpAX3ilT_+RmgN>g8@tO)D{JXv$C*V!J(YmGNN8?&KE!7f?7aC>)d1xdo6% z;JE8A`@NBdGUOdAQA^P%HGN$wG;u$>UEspCHLOktR-kGTFO{~CS3IB`mEBUrFHl@a zA&)9H823hVX(KcUDQPz#MvR&ra#9m0D)wf^+e3KmquEQjjaX|{ijH(%c(pXztU~js z5m{oyXu7cGG}34qf^MTzav;-8#^uE(@TTim_X3PlYQF0zDBz~e-SmiukD*{JdqlP-G$4Ipo%rMeQSR^t@Dkd zZN=Vit1+w8r7Xm6b{%WlhR})lm4(R!1v%G_)C-orgPjn+)Fvt=va^jRZEas!=q^S0 z+eq6NbVRW_E{-Hdg!rwgw-rB25p}+y?Q2l^7NLcyF%n3`FAhfh2$5!}9{)s$Fs>nM zNdpiCjhK`ycn5U_E6MIP-c*QRrva0|(ZEZ_>n@{}hP32}z=jctYnMh04f*&JEkBDs z;uj2r6e7U-6kvU_eU7q2|F((dHeo&53$;;o_XQY8qjYH(7O&3YBSzD44Ape*n!l9* z=iX$zxlbaE&tgh$GGuiOM4)0xk>$=$KzS(*OgD^TTo-UPP8EhbgUWj8yvYKpLv#_hAVO`w_drC;f^pBmGBw;rJ`AzRXQmOqRbg90m@@mqKi{$|jG+Uy^Hxz9Gz{jd zdVb`6w$B5`h)ItjBcmZ+ja#_hYQL-Ag~qR?Ii7y5h(QwKhK2ZhY7JlO4~ggh6)JPY zR^wOmv-0_u{9GfV>o^E2|LQQdL4bTWB!eWg#^lc(#tcoug{W>gBmy{p3E)#N6Wsn3 zNBxS%c50a>LMbTEULO&^$Ln~(>qz6*p_Fkt(z`l$2^J^F`w_6d#xL^D+N73mmhqE1 zJ7oOvLP4aQLx5RSC5O;bKB#6c^h8d+rU{j0Sq2DGvrfL7O|@)k$VUFQcf2#!D8DZB z{Zvu7k)=79MBEq}GKD()OXAWK?mgPRZ6+qoh_Q$Md7uA^{gcgfkZTx8#|M;m*VcNx z^5_X%dujz2udiLE27TT-wSBF5EJowNB5hyYKKR^lPw-eHfyB{ryz? zt)TIY7a_SWXkbsYb-WbqE46)n#IGnkm)t%wshoSzzF<>kYY4=dPF0^T3aIl_n#!J^ zC5M57Y5Ruu6x%|dwx77asu&um|0qBK;;=*-i{uwMBm!HN&auu<+>mL|LLUEXh+iTD zp_Pv(eNB2zo712WG~&PMzoJepZ&tm$hR;Dy^rs6)&V8c#2HI-d8ik{i^E0aI-kmM_ zw`}5~*da7Ep(z|`8ow0eCz2YEXe0Qhf@Xw3jSx>3BF>sv4#hRk)4QF z<@{7>OfS*+wM18qf$|W&Q&|WT`7p$81mb-~V49k$;y^hBmAD;yg4?L!X*1s!9^S5ccC-&2U?cO068ZgPw5anO*kB;&_ zNN7yOqnPL{K?L~y4;T22Upn&{-S*M=6+a&lzsKu%J)rBWFTJDbpfV9XnGIhI4LnV| zFU<5A9nw@I<+`a8LD2@ND#( zk{YR8YgXMarJXNSHK>&d4KyiDZC^HTw5c_@D$Oc_`M(!p3HRW=o%z%>2ZGg);XmqN0oUpMNy=^u{Dq+;@Q$jqp~~N@3cK({Jc_s_x;uOwd{u&2g(%` zo5J%R3<1)<6FG?c1j4!TYh-KtPKhDH5TQtc#%*@~RJ-rR4~1d|6NFmR-0`$FiQ z-a*j@R}ez7nSiKn4ABbdRXl1*iIA-lHM5oI+bUZRWoz9TP$VPX zX*BOiXC0&BBcMrW2J2EN61hK*P^BSDI)WRyMl*}gw+#fszDYe_Wxo*V$fGI2zC*&T zPGDOk27t|7VucB@-p2FpAP_^+-!eN_ih)qqsNeZoE3*bJBAD8Pr9j2BK!#>~wT?iK zv!>@oU2Bk-{iwc&pPxudDCruH6lgcXq+z{l6wuNj60z9)JjXrWIH9V|Y}n}%Ak9kM zv_(Q=mG6^b)L17LpR#jH5Fb{*_>ki@4n&;`@gxjcbj;u!-wTjq6z7J(l6%uQY2c1HY5bi~@p@LThYNnInXT zxXh1a=B2W!B8NphA5q2I%wF&AMZ`t3M!0UT&H2#QH|dD@<&E*Rf1*kgyS+D!p$Za# zC|9_p4C8Ug91?wwLOmZy2e=_ohB?4Ou#;k^L&gj4U+YFjG8i!yX|i+8@r4@LL;x2r z1AO`wf?KarvQZCP>9`Ic+7a=4yp9*V9yNZUp&|^aay=?zIt;=$qrnCi^|7Y$+d;jX zO?)2Y+FLW%MkclMco2!ig$Um)^w?j2L<^|2At-F@KZRY0D_^_Q2-L8z^Bf0+C%)(c zoV&8p3n0ey#5N+MJ?d*KE@1>ntv~8P!;sz4&KUwIMx>ia(V`)aD9;d(9G~J+AKzbX z-zA3{Posw?z{@?_CPf@7F27*|7^OIx4c8C_%nP-Bb5q!AUQ&909SE^qDAxg6;*Rt7 zz~nFlBny|+wa)sqee-rN?kFx~Q6W()nwOGtpuE3bK;!O8BT(*Orw#WK`|P28FW*?h zwWro_`R2-un_5lggtjjPRHF`Zac^zkG}W`gSfMfmP>eQyVA{TQ0&^}D@W#3TDOYBP zK@QSa>p;I>+P?MrcIDIZ9h3s4{ys@OZM@ramSWp*h<3VMTCqZfN}N*&Tv=cV_hO z-iS9Vp`X$mv?y^DjbD#ibcn+Z)I^NM&xnUdJX`q`TjRI>Ij8ZMh({xhM?>ROqh9US z_%$_70S=`Fo70pI82=U_p7%rRk*#ZUqIk7}l0)lJ+5#`=?a;kwYUR)jZyS z;rSIIU9WN_LR~@K?6|m~b9iupQ_DSl_{9UCg?$PjYHIx*grKur| zGSth82HSECW#wEvcH|T7I#g#QYDV+Yq(l=Mvg->2oWHVC5x-rK@Py`IsD){2;jxZJ zmKZ@~T9?)Vm1z!o^FyM;og)NcK_}j)ts|Wh2+N*?Hb#hF ztPuoWrfeQ(rSPgD`ZcZKv7@)-wAGd%e!0dkWHrGL-KY?0smb;1CD4k{95fOI61f#p z^JX-AGf3=?j$@99h^S*VP2W&65%cj4Wwd?CIXD&Y5pIgcFCCzBPDAMKqwTBOBd5`% zs_uA9Q&#z6dz{SDM3Jy6>twV)hmg3I^o)nJb_ zX2UqC)Edep5ZEnp&xq4EM!e4<<;wY@l!Ugg$IMU^JJaRgq_(g0N0uhDVDv~j*d|Qb zBpYSKd8)GK4l_a7u=s`)Mh&tNl9)ggx8qH@IFhvWEhKmhja3j@z!^t7A=Ng5XuxV< z+U$^e8=-k85qoSMH2Y9O^%-)}lzbtBs#qZAh@60ef@6VGM<|6B(d_fzk4=o#`JUSw z>HiYizSa3wt{YPRz0$bnY0i#~CcHrl6{M8hKl8e5_XC2VIjA6zD51?M?Z`0HL{-BM z1w5>`2=`JX_W`T*j3}5T#=E9{UFLaiG&#XW{*uG+hVuMP`gyF;T$JZ3 z&P)NVUqnOGQI%ed!hWe?t(-rk9RVhVqDi8VVjqAZCJ_vcM~f9rvmpzRj3BimK}Yf^|(v&G8ZxTAOvP1CO%n`NHJ^KK)8DP`|ze zlzD!_7N~SY{2s641+OEGU+RPIyPAW0>}pp#tQ+xaA*k{NQ8)39c|^fd2$=r$PT`dj z2<$^6_mYS>JJQFr#&1J)w6G&7qj?Dnb-kcR!_#w|OA6asV8Rk47p|}2@wZ+u6Nfnj zpGe%JuX(8p~r#qayrD1yDDRMQ=U zg;NXf#&vTBu@-%!2`&rS3$%T$?QN}R^R*N9fg*n^6OsM%x4*WpIP z4*aTxYa>P5a!1_7#;Vg}K0d!c{VC2dFQ9PpvNdvJGf6ePw;Y7WMX4XrkzQ@2@i zfMty)Q^6JNMhy!dmvt|^{(Y3 z5D+MVXhV$8Phch5Rn1MvA%cy3%`n7_8iADD(>Chz=f-8Sf9haG*TPjr-Fd+H7?1` z$f(qQ$Pg8xrJidJP95Ha<^ta$VQTGMw=tj?2vbrJOr#f{yR=VvCw^szX8T0DL}X@f z5DCq%XulLqlg-yYZS0+f zj`(dFj{-!gu3-muftcpT2c+D3qvUvJDE3=afqw#P5Wfv$L#-VKnRv$;Z;tp%`6{U~ z<}3%wN+%>5y|~$=10#K1>e#9g#^{t2nuFXfU43E=H=a9%%MY&_1Q>XQBdMV*w0U8P zwl9c(PdZh$%k)wxWRUQvQZ1w}Z#*K8y4uk8)fojeq_3{+ODtZt3g%K%lPF;5QS3&s z#-FUl2huew18rY9Z*rg8V&uXNYQJ6z`EvJ~ zCN5bLD{TN>he@!NiYcHdjd&GwUbjzUV^@RJliSQ=Jb+sB5j7+@*NP;G zSgbXfD~;bmwN@q6nx=(m+=9}8>cump#et5NA_+gWI$^c^Gn&`mbS!Kk$EDGVWsPgt z+UZoh?KH%sS&0nDR?|2ZASiXtcT_c%GYGPXVFXfIkalDBbRO~=l6UK|My$7uU8(U2 z5R{}Kf2|ljLi|pjlkD@X{t$F&4T+#@7&bEV0U`}##_Lw+wI^9K9{@Fb)HBXOamk!w+fi~DHnekkV=v_S zUc~EUX#dJ{mBy?=WzM5i`B$%gLA54(^mkK!Q2U>vA%5{8gipOv;M1>=%J?wfxdyg= z2TddVsPTKeju$+5#4mLsW<97s^=YhTwDw?+4&7c2!W05b9Q<(~q$t6n!l?eo3lODX zB?90u2w#u01``@4!XxvJX!I!4_SF{c1q?45uZ)&&f+{E~BpEMu{kd~+_30Cz8q-6t zQ|^6Q>aGM8@4{_kA>LB+LzJR6Q4yu$XN!W@3WUpu&>Xr~;P>xz(Z;V=w_;rzFULh|`xJ0eeLdrp@ z94?9vvNYV(VgD(I*w&~ct;Aeryyd(c20Ab4ilPWql6e;b%_Z+EZ&7 zRyHct9}iz!-bA{hPE9MB1NPPS#ctH8Qm9lI0z#^O zL&PuBLhM7l>Hao@AR~;|UltjLdq21J2(?2TDZst+6M`XH+l+TC9hcV?U5a@25WkT& z>3u@#vEdpL`Nw)2+2`4oaZNb%gCGnKgTl@K(E?3nBc4G?xuN_69AH0<-=-ekqw%{8 z@w+#|yx0Dv_mQ9fe=QNe8sm!An2QMwV=dmhpNLr>j7PEB3K?2c1a{;_p-ZJnKv?9GG)h7nL1zQJE@7`oMxShc%>E< z3l=&U>Q44T1jCq+5{c%o`mV>}%m3_S)6bv$=w0}cuX}GzKDK+1)5`;V*}wlNJomL% z;Of(-Fq|OVeYL<#zkLUO{M$YNxI*~OUwdoyPCxU#&%=9u^2Pe`ti+ZW|Lxc6cR3$0 z{dbG%D8cPdWg9tB0m7j{tNxk)`E%jPKXPgM`8_}RBE0jvpKr#!&`vst00yAC({b#+ z1`>%cNByOm1_gnrbTZ1j)F74Gnae1`X*XQ+y;)%6yoq$XJh)BCq^fY@-w6P2y?6#! zo;tBL+hoHRMTzisy=^<@>UpWxvxoRK3MrhDY81DcG^e)qW7MFE=7(6pwUw-8Mfjb) z+jbO-_XPHHsUg1AcB8p)%sWjzdsKA>Y6Uflsa=URdDQEeQi$J5JEm&6oqyhLe_IQ( zLd>)GsgN84L&gyE1KzP-h)iwAxxQBeOw4Xji_WUoL9Wp$VTIf}zD*s={4DUo#%>)B zV!bu@$}HC!PEtDz#(fhP6J!FzL_; zr`B-!kyVXE6zdx_cgM8|rTrfFPedLlNXl8@vXKU(Z74hp(K~n(7F47+<^WxQ1 zNEYadNW?1zyox%1Gy7i}gl6y{>_I323M7yR!?c+0Syn%l#|2_roCK8KKQdL%qMq z^jFA%VJ#v=qNl;2%9iKZ0aRNyGRJJ!bF1M<0`XgEGd5b-b0b=9H;kzgcEfb%P&C|dKfCk0)HVQ=fNwlO=S9WUp`g!cJ)UI};Sdz9xxqDC^QLITQRC;|Hw zN3xIc2iPo3b^(m4xN%VYEsxX3{1^;mOQDSCe=_3p-T8Jp=T7lc3q)x4w^neh);|gM zk|0q=^}J1WGNOjrikS8!7~`!qis;f{=)}7WL57OIW&V`M=hA*m{dVwO+QO#Mm?h7L{65BY5mFWA z_-N4ju?xX4=-)5nQDpg@Dn8+H5E$`4Ym%$-f7;K4BLB!Grsg}Rgx47FcvDzm=h@p!1=tP8CsqXvx2g^;nLSE27wSIg_@m; zhbY&P$}%4&=fS$m`D`0mn4NYU=(L`#G}*YW2XQVG_P&an3=7Mp5an^oZ1^RB8&3{! z^T`2T_{fI+eS6N$G}>8U{MS|C_e=l7?|`TO#2Q^89*;nA5f9^xOpcU(=dw0L`#&huY|Km;5zqxm- zzz=`LubB-r)jy|%Mv7pblA18X?v8|Q!;|W9aA&}GcR)C~IKaR4W1j~PfA0C}FVA0H z!6R?F;5GoE>`2GmL3Q_xjg}79ok{au9BSBIb|;Izq3VvteH&HsdjjvRv~CW&N#l0{ zTNBdN5W}|P%Y6PRxEP6G=DXH8CVLX}JKry2eg>%S&EGZ8Z8VCBKOn@NgO|^bv-94~ z`R$iCaQmeVTzGiZJg!MC1N8x6>;SIryLCt7`tuM&qxOo~-dI2XfU{+)?8a`{jj)Pw z8PeG4$nN}O$A6Eaqfh=k4a{Kdr*Y2=-qSNXg2*f`nQT!eH>0=^f&uULD({gq1kZ96~$3(*D+s~M(bqHJ?% zYK##X_8QUOhs<2KjbxSG*zJF^{p>J|_qnO6ikGB48Z;DB>u-ngX9_{$DTEecF5ctF zmAY{XTFo|wVn6`lUo{7+_*<-F$%o%?0@t5Dfzyk#ovCsgL;XH-;m}ZP4&uQ46ZaEj z)6rE4WHEoR8fp8)1cR`fVt$d^LuNbLSdv_g)ol{9^;>oFgB%1${XEhTBkK1#{>Nz@ zb9*$$^PYd1_sjM@Tf0XNVqh{I$ljCYpTR-!&1<+_hn~)A<+l`C#o2C*ahQ%6po#vi z_E&?5ALh75+}Sxmv-eGWL&oRJdc`$!za-*yT5lR+Z^$)VqjGqX z3=mf3K*mRd;cuI`anO*^>?_212(9zdAmVi%hoFrlLmBTyh&`0jX?kULS|&!kj%*L3 z(?Eb4ps;#w4B}mMHGX-#SH~N|AC7UvD(6o@%nJYp^Qpw=DEApoMYSnD!d)J0e10w8 zGN5{Xt?sBFf)K3uPCqBYfX!!Xc4AQP(tg|VC8D0+na{Ceek`9j2`Q74o<3(FBx%fc|G^GEAZr(UxMHH`BxqB8cT~r9+dLb zyaamZKjMt+efDiv;9vi5o&x}Y_x{~az|VZ|^GfTAE^s0{u;216*PEg*(6;b49wMy{ zc1~pn94W*R5e~w)>QF)-5eNZN4MpeN*qZeApZt7y^ex*0_TPNld*PLj+*33`^odXr zz7_maAVL-^*n*XB<{(I;1TvJZ%G z>!mX|e{JRH1n)FL3PVB&q-%r3X*%e9Fgy;M}v5$w}Jp{3u#UZJL^QJ!tI7q;8zh|fz~Y8)Pdvr-_9dW+Jjl7 zriopq**~G!H!8m##dMvfwlCGl8&6A5^)HA9HT%r8p!?VM&i5f1n(Y`%XQwou$*}vi zzpc4J>+dBM$wqDCI+h!rVLE*Un6=|Wc>{a{dcHptuhBsj-2bBF)wd4TJ0IE8CZu3IfV;GNNe zn^rQPDD&2?Q-gY$YiKEypIWiDM!p2vz8F#WlZx;i5r$f)I!VN+vY@ueQRZI^#Hgw{ z$TX_PiRbH0j$8Bf6*5xkv?rfCp(xB3Qrp+ld6Njg8ja8*fF%N@lz-cV5$7RDAMyS?!9Fd|c1ue zjy@8888uy70@x5k@XX8+NHA$|*NI`NeW)-VjM6(BV#)WG0cIS3Q+sfVb2UWT&dv$M z6h|D@h$(8AZ20RHMZ;D4Ee?8|sBcApOJ(-~oS{rW&E~LzrlLZ_JX1t`ClRC49x4gi zve}`2sYSw#j0(eQ{&^SxPA($AErPRqP&@E(zIN+rSfNANUL)P9Vh)@4QK1@P$?p_U zP9oNRXhI0ee9aD&G@dVp0rhyXP=olc8GGtRM2<-2h_|siIS9s`iLr>;Z;sq*?1W@i zFwRePrO;+3tC+CVkCXeuxT82liN-M~ImRE^*V&|X%AAML(um`naiqUS)mz4GH-LL? zn(X^PE_G?9c%`R(>XRr+DrS)nD<~n)ZQw$nApYe*Zm2F$IrC9?otTXLgh5qIoUx}l zU$l%VZ7;t-Py)Hq0Gx5rld@@%?7F|Q`e#NPUm>0wWTRq+#Wak1u*eX@aygdj{q^T2 z=e=rX%W<7Kj8W85nM(xi*jOZ)gZTMS+HY+>QyS@{Gneb{ukRl&X+vPgN0aa7i5%se zNX+g{3@-{v8P7wic`!j4-V!a&qR9lpPc2#3NcMYG?{9w)DGdkXKfCe705_k)ZSl8F z3or)5Ca|sUB$!~v_4m3c{QmJD_>1u7Z~xN(0Pw+g{VY89^>2f>{(Jup{Go6DM)>)^ z`a|&kpZI&nD&g^h&pwQ=-&u@Rr#a3DU$fP(Zsn65?ajXX?x*0#zxCIrghonJ0$Ils zV1;x;cfB$_Z@EV*MDxi%a=Ci^TQ8o$kAK?- z;N|D<&VFeF@MGWn8`a-^@^|k}9d?99=cwB`)OMyKM^aFIP{^&1x+Ck_v*)LxuZnoy zJ@`An`*}FIw3_DQ_Dg5G@rLGZQ&u|E64v@7vODY)gw)j0IDqKT+I7^KEm{`hPVZ#2 zCG+!W8EwOgwJi|SIN}<84E{LHTYy*`?~}GK)CVP;kiK)P@EGh@h;_OC z0u5If>Kq+d2w(R3R6~~Q{WY{jL{K`~$MrR=P_QM`ovN}!a!PziMh1!aDm9`)&dYQV zFM1^qMMrP7-L&=2_aBkjk5SPfYUTo!b!6(sbxDBoeoO;B~mq2m>;Z_9XU?+%!$Y zdZj@~E@J|>dsdI{AmqQ$MGo)QAKBEaO((eUw;l2I2-*HQGn|B^%j^tZTkS15BS;1Y%L#iO@#+@?$X>npEWZLn$KJB3ufPx}I@` zQ*ooJ*%-)*2Jtc?orS;UaSjo+>v_fQT2h$%L23i`VVDtfQ{>7LQkmHrswL zs`EZ4!|MAmps(#aj>q`!Rot{iCUPVNHcT)(yFSc}N}S?!n*^s80D-fAB@*_!nHRxu zg97QRG^Qaq2wm=mic&Bi?H(dv5&lUeKhf9+ql{_UF+A{fts>P27H{x*kn4F>-#L>8 z0s^gQMwBx9$L9|j8nH=YN`?mz$l(d$yPK!o+)r`(AfU$lgLbHF#4$G2kf&J1hJ!$W z4D39@W0eZ2%o>KI!x+bxNN0eVes9I!E7TD#9FfQ3umQnApx4Nq5y``;r{}mrgj-af zLnSl7Y=%ZQ51bfz2wF;^`MFW|vtta)a-`aMcCrTsbHGS*J*-hB-Wt{*&a>P5xl0Jc z9fGsF+u|@xak@Y}E9X}pJ`XF<5rDga9DrJe5uEo8DDgT+iI1O!(%wi$wlROe5tjl{ ziioXdu4IN8?09LMS6*nD;=PH9XGS{X?)<3c!*=sO*&T~9Z$L?eTAiCDGHyN}&i0g_ z(JX|QU3u3verF_OOD$WWCDCUN8Vp1ls)$&{jt&?=@%ZO3MgY_q@(8`cIfP7X>U^NW z7(jD=CD!)!#En}<9#u=*SkGw05n2XtdfpUL)5y0$1yC02L$l8gViA*Cujm6OutH3u zj%);n`d)bkZ&_A_O4OyKtodje-q6q3qR(JhovqzklmEJnQq(M+GCIoA5?+ zJ?cydTBC+$&*gi}OkSiyziG#UkY^agn90Rk*XV38R5X4qv3Nt{cl`a$Cs**oM@VO6 zs2F+l2lE&-A9&&SYyaAR3ZM6yx_AQ0jwE<02yLy4BDu^8=rq3zVP3C7~cI8AJ;V{ zOvEp9!~p==ku0e@NO$-3xdER0+N(|H-J}`0`@f%h;U0YIg?p`dff%@RSL*2gqrdPe zc=`D|!f^up!TWB3Ru5D){ER@R(LkcQQ`$vhnOw|VYo`F>Fx|i4o30u^4=1YZ05Qtwg5!e7i zq-m#t$F^^&kvKJF;comjBswXGC8a~IaKw?N(S}lY2~&VS3qU3{{LFD_`gKTL;NZlkt(8@`BzONx3$4-z+N;44yn;jM*K*i#B~-z zLTWNIja*wwwebfs9%3g3+Fd}YCi+lnt-k1JW)s$9#zx$B0Gl12q)f_Bm zGa_61+x6)-v3+(IU~`wngNBA@H4j|lS79Ki5oAUlg`a1coo_ULi}bHL!icYHD}P{2 zmq6H}d%PbEjbAq}p`4#+Xf!a**kCcB1^Y>rC; zp-n#WPb=#3#zPlj*~IIKe1ws{Ez}m{^mB#K_SP8H5(w?a@5uB`p_b`FjCa*1Zp7%C zM!04_q#q)~erTHX);>}zdR0EfLp;aNE%V4~KDP0%WH847Yg$nlHK^A5)e*n?>#Aoc z(|%CpPBcWbmPa3uDh4C&Ux`7zIfE6*^V9BL#dlaEiktimXNVv_k2js)RR$LkcMe!< zR80jPt+>zr%VBU-v_Q1)lx7uLddwe)d24e)z!u z_D%o*c;&;t13&d0-v!V9YhMF@>@WYvaPtqp3BKj;|0w**fBQecKmWlWI#vje7r@ELQuQ@=yyI2A9&}>aQD?st0`e3e#1sTw;_gDPaf~=1ps*VtFOSx z#i2S7G@b35<3Z8k!Vs_gI=fAFhoopNpqW1#UncVO3DgRVjby1;}mzw-_Tt>dmmOpQnnp7~IzZVkFFX&!lpIzhWQ8{{uyTM(9=!C)a~P#nAGj`S8W zQ)`4_s^ydiy%NEP^*FBtV#?BRLag-atVWcH(O|VjETD&&=ZVpvA)rAtX&NS6>?}f9 zy9Hpy-0Pr)DhWG7=i%N)(mUvyyzo1 zz}NO2WHGrx6mTTA?QeNUFp;g<&b3g{IgA6;9q$uDwL{z2)o3;}rZtUK9Z}HQA0o%k zVyzV^5&kC^26*_H6S(%wiLn2W(DrS=a68Uilbs`|GIV~ZY7|Q(G06Ci*$HwG5x%zX zyfp}Pgd0k2UlQ7bI2Ua+i6#)5Q&aNC(5P!Q*Ra<31w-4nVSuO+%Y)P~Munn|Bg|J+ zNx-0JTx^IbJZ)d7HNtB|tkh(#wS7&B?g@TY5Ufi%Aktt&Dy)h zde*dkp{5dSHQ)pn5^^N8C`^b#BcOq3nqIF)N{#k!h1fh(gYYMf5kR;ap$U#cBesr@ zM#dN175Q|=lPWG&5PP+9k)>%aG=2r5R<3IR!MI^)Do?@wgowvR+m{%ccK~dixq@s5 z13@nB+%TYXe%&D+$vZ#Q5ED(~x3Ysm&yR-vymY-vR=!N2?Vy74-L6j z&4-NV1j5(WD6Q8bIf%UqVQu8Y5Db4yb-~D@wx0Yg1_(>Zh!mYKP|Y4T%nMdyb^AQZ z=VGGht1xba@jfd4mg;wNHLKBJ4BP#Th&rxDVY-YoaqamsG^Q=!xcV)z#&1O}pD4h$ zc}(3vBIe!?5IQu4mBKlW_PX|4ym^V|&ou^!>QMwkCufY*n8t=dIbWcC&gK4Mjt@XJ z%o>9WYK(_yl#$Vs}=(_+t zX+_FVju;wYtmF0Df9eb2>NBTP$J-0d}S z{g>e0txYo)|L(iyXSA-5f8aL!=)duP00Q``KmRy<`Ja7k`rRi#dKZ4=UwtnC06hAK zFT$Vsi9ZyM|K={i4}I~k!Y}}Q=exhC`rXfb&-3t}zw@GApg@4n`-W@qd0%%09{Zw; zaPh_(hBd;iPn^NW-hB(+_fs#yZ@%+oI6L!A>$CsLL+}OPbPXPV>qWTy_zA4fBi#K| zftNmX2R{6NzY72F|MUX9`n&h4uYdEOx(?s)gKr4m?SKES--h@9gO>mi;oE-fbK$9f z>C*JOcmJIi;ho?8yqTze=!p|}>$g4(Z}`f~aQ)eN9zXqwGx*qhZo!BC>8tSWzxyJb z-O^PM-}24Z;ZOeHGtQ3=MK(yq{cH!4qF}9#*G=(oqa)tx!y8+{zatg?SeR zlFp_g(L3q%e*kq0vHX59-s$l-5wm*dvWnO-wIxwgVXxB-6d;02tNrEABlX$&4cA6CeXyrN~DIGtEkfL>i^QynoJ)d(UTRp&7_U3o+|WT&3A-G6gV7eBnnq z64gUcbLIvOF>Agb-%lO>vDQ3}H-!B;p8dxPk(#l8Gb0R(7p(O>0MOaC_-OjoBA|ZS z_=|8f?D$guK^@-}qFJ*PQQ7CZ_Nr;eV2=L>lnC%P2~L%t)aP|Z{##zw>y{ z{tXc3vJpHVg2>vauaNsoC94mx4j3K+e(&TH16mJd_rL+)R*X$$qgZ{{Bi zjcLoxHHcux-YNNrNJCilyN(pD%zWq@Uz)D^W5wq?ow$JYSCmjM2y!W4-Mp7c)UKNFAq^TBUd19^%EVpb7y~RS#bdP#sBVO@D2a<8>j#F z#czKE-u>gBfR}&gZq<-G+8+3Bon?$#Hrf5!8&2Vy{y+BKJI=1E>i_@jeWy<{ z$;_npgoGq?LP$W0LAn$H1rZQMP!Yt6f{KcT$HHTMP(&Z~5gx>bNH5YsdWp1T3$wbwr92FvexeRX){b(onu_nve1K5MVN_xgO^|1||8X4ZKxA2OyI zL&j8N+L2Az`oUh@^@&y3{zktScNF-h0$F6)28`LK0sEgj0)PI( zIxN1kJ<$+i7p~9*DpF0L651NzkWY@mffwlQIIN)t!zR>V#xcWi=qE?xp3B!_`9nLs z5xKvMuVmE?D*!M!@ca>2aQRqv2@S1Pm~m7SW*pUohpyUy=YFx(ugJFshhI7d^FKHu zQ7^LlKDru1Mpt9Xp+oSNkB-KjpIL+D5AO7hjsi>*UjZU3ru8f-b74WLz_JOj{9geAE(zq+&EO!HQ@I7LvE%Zvsqg5|!<&N|YS6 zO~(2to7_`aK_HZHB&X66pT=?t61b9+c#m*M%)ULIr7B0mKY&PDp{5I zQ7~MQ9(nn+6Sx({y2@sE6%|#SNlJ0P7b`&uqA)-zLad&@GQ}w=`M;~!bJ~TmeckaY z#O^gtM#YpzilWX@^jc$>;#aaDQwF)w6+$6No@8NLJ;6@ny2*5sam`g2HpnRqj!JV)wys5n6x@*Sbl`IVzciq9qDtY2dQ zD!2?`-ytAjf?I>CTEJL~fj&YMV0L5SEoBB>{#M{pvhz<8pn;njsHO*akwzt?gHbNxI<59<5b!14LDKg zj9+>FZ-(*f#&?hLYq>&BGJfUyqqW(LVrzJw5oG?FjKT+JBZ)g6i48m>7_koXH~kD^ zE_^m{^pu5)1sM|$zzT~fx3n)9V1QJZY)m} zN8_^HwQynl;ZVEZ8Vgr`oC!FHu=g{{^CSD44LOWx?Gad~W$*0=U;|w-OHRUQOb%lj zA+~L{!r5pHXp_wl_7$_?jpdM&rrw{!j9=#bsI9BS-1mSZPree|nGYJF*jD2>&u2nN zhq$0-#JB>sZ=wvwulc{t_^ll_6mS2ZYcO)}*~!mlzvnaz-)#yWyW*Q@U$HXyw{1&T zVEiHTP^hgzeQOJLu3ew`S+m}K3Z|cYtoplXU=Z70TY`bEE-bm_E_ANn_)p*TJM3$h zW7xFGc<_^7MEB;c|K0`t7yQar7>4933gb7{IYnT0dMd&6@USbxq33DA3Lm~Yp!pu!ihhcf_pDpjU{)qB^TY>F^Jls z7Bxc(Y}2vWPZ;PfmS0n3%&GtuLFvAb)+-t$^tKlPP~iPa7dF@z?UJ&oX?zXN`0aGm zH|K}V5wq%W`mcAxuiv>8JJt^*%eJ-vyyLpb7_)B!dwut|LG*V;7&g8-S%9ssw>a+V z3D~i&4;xHwUOO2#y=Mj1KGzjQ?y8JV#(*q8PY5X* z4QDFD{~(QFU0M*~AKTqFh#{k@e0;4_d7s}!IomgzNm*i#WE;u*N)^xI+ffjdDtgn2 zDCNODO;`~tnUGm57N70wGk#0xkFr9>gt@3`J88?Kf%u&v6d+eYkz7xuN>v0HRZc~4 ztoVq^lWMG2jGCn-)JT1*{(fOzZ%oO||X>eC8}=vmi;zcPaebNQM|^mdg8FxnkU97?vufD`R3O ziZ690Moh71tqplSe?XKzcOAxMN_25B9UtE%ovZFqlT*x%Tk~hT}1~UU&&C}(Ihn9#`ldl zgUwNdB^%-nBasaMUWJq=juMU{&K)+1=f8ZM6~A|v1zCI(it2d$6-psF-V&wCr;wr} zW!LEn7WZ63id4ox7s?wJ2PC6YjK6Lth{tOlzl8%z#OE1o;ubUNwedqq{8;)#`T{E% z!5obHA1EuJIq@!KtTF{3V=U{18BviKhNw_gTAUv21qKI6T^}ORZr;3B%%2!D5q!KN zLXb%+b4UYyB3&wu^$Y6Qa6%OboHW>_mp{7G8_xKJ!uXB*rLO2KJ0cYvM-8b6D^jUg zD~=lp^Vem-JGS?A}cmewlaT=+eQNHUEF?KitEMTK9 zBQV1u3bS(4e$C?`L=^EW0s@Np46AI{ks!=8NAA3lw_`@UgJ95W>w=!*SDZKbR^Wa< zp3gK-;(u1Wcg9(mJT`V{{Nm1Jae*Ug!(hOF6Hexf1mBSpckb!_*#0<88pkP`g- zQLg8-jx%UpYtXhiVu?#u|3i@U{7>Hat!o~R6Ry7ot+RIr0Ni)sXV5UJ6$gImB8;6k z2dCft8@zhsAMosVevE;xZoc6%c_L<qr-UB1{nuV&`+JDIVpLpZ7=xg7BKVN*=zjs*ie_!!Sv};m* zA3DSHQsiJ~vG2JfyiMOVPj=#| z?{C1?#l0xh8caE?35Q-j4h&u0tHwL8pO)BO+k(NtmyE_A&wm33221blz|wm=Fm}HNocY`7?0s%Lbt!hN>u3GJ zXmcs!cdG-=8|iKOuKG(S9{bKlY+2NUsyd6QZykywzAz4rBhz*~=Bf!;|3VLTtm{wu zTqK;ltLrS@_QkOn=!x*`&$eL0!X7k^s=~Yvjzr6DwT`0D;DGnH;?7HZlJu%jZ7}bG z5&V8#xgIay+?H(M4;fX3!#_I)d!5=`=EoZ>xO^;bJbjs35$rRZWCf~F-UNG`9AtTg zm766HV_Ngax3|5B{_Y4h4OW!h0&WFakk#o{1d~wU1Og9NfGWa0S?a4Xb#fF0zJkxF z3^8Mo$;u8|Et_l`Z;vTzW0kPn2#2rG4wckvj9*gjx5Hdc7BBM{pis)Sl?yVmBHhaS zYZ7}KS6OBP6}oOoXxM%@WiFdSneArD0ksN-7^58PR$8D8_Ls)^)mZO}6<=k}?k1U3 z7-TbkjT;}xWU#Bj6vUwlZYcrP90@qHxsrHfoJkTg3pKozxDe79Io8%B*i(VcUsjQ? zC|`q?N!DFJ^iNQ-eI-LoN{W)_B*cJ|mCTeA|BcBdbL3VCL$KMQDRB6P0R_~lzS6{a ziy3u8Em|g3qiIZ)&!(4*Su2F0Hq0J$$4~McXoxc-gZ5=|e324Fh&U=3v81zonUbzT zwy)gqa@oEJD5M5(m2~URNtNyn<1cpFtQ+kJ0e-lme5tK8dCEa*g&Eol$Rj|MC_me zt1T~v6FB3&f57V2wM2C-5Ca7@a=a}-XZv!dvaX0A#TLQ{X0eSG)h3|aD`bziUca=+ zWF|_+Dg`BK(AzJ-B=oH|4RhK^@^NT+jPaC{GLv%Sv^Bm`SySc^A=7p@vCj@D^x`8!L}o#}gtypD{Lj{*)-&DBw&JJMowdi*LmNC5^e<*#t%u;t9(~@)CVvx9r5+ zC;|#mX$~9TUmNIulJV{a2wMQE8vxVi6mL8I-Bs)nV~%siFH^h*8NZb=I+JcLn9CJ2 zekq^v>p4Og1e{DX#;?!lG>+pE1@euUC7t$r5x)z<5lJv4$qH3oKyfMA#Hz!XDaS)L z`=pdNtSFT=aU^$RF)SW2g|ZQOmSc1g--d$Cawaqm^Ij+#g~ZU5C6dyS&~;ogN*w2o zS&k!(aAdH-@m9}gO@L`$iU&y4c*zEm;ofnn(w{P228~fRkXT=J&YwiQbHCTtJSlr^ z*2j*B^%UY8nC|zQYmT~}k`+KIj4wc*)vw8ze+K3vS@T(BtdDsNWuvYy`-)dFUYUG{ z1AqQmfdgUCaygq=5LW!k^Uh)V%6VDHu*&G`5#GkR$=E7 z!P@ZS=I@877DvoMBUE9`6xAkB=kJ_Dp~|3jd;x8ni90C;jw;GaeE(0`_^lt&f)j7} zF`8#gL$SXf_gwHvtb5{F0Kkg79>BcIF2StRPsU#7o`LBn9*viO`5Uab=K=I=Yr~|s z9*jf3dO2!_mNsvmzWSP7+ErWg=#%*Cb=UiwUqc!(^_T_N{hcS`i2wWwHa`Cnx;Jn6 ze_}(?R##)(`~%Rlb^E_}SMh&a@hcTZtO7=qQ1Xg72nAALVAT3Y0q9uQhriyi1#`|H zmHhmW&yL0KPG3@bUt}qMOCL~GZ!q^mquH-Fz0{4{KD+`{EG-TV_5+LWYQwhWy*Tp^ zGm_-3s?K8n^G4#4udibjJ(}u?NXwiksCrBleER82#-PcAn-MVa;6{wypKsVc`i=E? z_2%u#ZC`wM2Pgtg_+e_JJ^9d~XqjG%ZOcm5^yB9?pmWne^82o>gLwJ3sp7w5T_0Y& zehU_SVSMtl3G;`bPz4MQr0J)2ZBayta?w@zsO&kXdsWq2%>T$J_L2>M?Z(aLzk#UC zDJ~8a@%kO@*tWbE=l*F1Qzg&6U<4kxa-HZOe2g4ExfZ`Zb2-*M-<58{-@gMF+&>d_ z!&Bw*#CeU5l8P{VQY~tm(%pShg!3wbh4pRHNsMG#gsrJP z#$6tBFv#>xOAIM~UEE$7js{Gz7byKq+s}UskLeOs%FN`7Y)=8ADY(gH_-V=X% zqABBk%c}T{%H_1uIIV2Yg-VqbjhaIPuShQ|5aYZ-QRHY!#w;~>Q~~Nw@z7>}L2TUB z7bd-m^T6}P6*yc`K!n^0zr3KKBMNAlRE_%Kg|ecW5``|=O4z_^Gzf#nw>4^^B&j3K6sHJ;L~0{dl%`GuCw7r`+DMpp)# zNGQ-rUsp0l%mc*zCL=3o$)BzT7|R}WHA)q58kAQ)PcpxAk2(a`xwIUG8z2>K{usxU zi$)*5FEq_`h3-KnGwIA?RuE54!HTN16zeKRcNB^o91D*h zLSg9^*kuHcOeU%b@A{Zcys}$JXyyG5-RF3%#AFp6yZNYc-c0D7FwFz`E3y@|t`)Pc z*j3s~i67)#xV$}leMU!dht_67j9(&+5ia6}$g{H)y(FBu+}(ILA2Bw=VM<}TlW~;& zQCgJlB2ZF?fYNmlNhbvEn8FJvWxgZETU9~tI;JQc&BPE-P?7wy#pEYn+8UmzhCQu~-Rn(5a9n zQWO=|sO-p4@r%43$?QuKGUIPB!x-3@MHmavtiORl(3HDI+;3fCA{AIkrEZ|YQ`=X} z%2nd(%@DIaRon`;ube{&#frN)sY_;zH(Aq)@a0~CAs4UaFinDNUxo4Os9HeCWW^Zc zFNA1pUy7&eCi_`6ISknnD=cL+N1;+;*X9~ zcP``CsWc`sVMVJPKE<;wSzOjiwy#iPNd-HyyKBOv(RDl|{3Moqo-8Ep=$G@I>fkO|E$1eESM zMGqd^*JVb!F*efsx6k&)F0y?kV>UZZnDW?e4OcKqRbOvdelSu87gtzNTP&XeBxQSqk+7Y+uXr8HMd@sXS&l3rl1? zLlLRuDag$NkYJ)3l^K~lX9aLa8L3#S5{l<|&n|H;2{7QSWUUH@ow7;KhCj>qEhKw7 z9#0c?Uw~E3dFx#1n|`+A@*l{c%aYAtLPq1bOQINJl(_x@HY${c7uBiSvaGN(mNv4JG>BX7M@=20f&HkQU=x$3ej{lin( zQyM2?#k$vDHH9d$ov7m6E7@Q%x;3+0e+{veL)R#s<4mBx8BM{Rq2Sl!hQCp1LyT}E z*AtZap?hDAK7lkD!b$f&OUApLOH`x`)tJ@laASf_P4a!L$yl&AS@7!$t8P4XsP2FV zBN@N`e9)NOv&r~@&NR2?Z%{lNXTB6#kF^2kHOG<181BHMIr2zH!X(aF&iM7_?P9zN zIXbZEw)Gjm@)$Fs({a|%XA&ok^5<058jKuYz|QT(lrin)*ZTRp$K?I~k^5Lf#*f8` z|NCP!O&pK@&Mw^b{!6fV;VbHEx4iTkZaMA!IO0FA#Eetkj$&UwRz3I_s941Mr=G`_ zR~BKP^UgwD%W(Ylr#JlF&arLj3XI%)Hfo1AslOk!@17XG+Y}5LI~t}?K*!3}Sb5*W z80_s+Utd*MixGRx#>l<*K(W6M9dE40#^?WvVqbs7=QMn`DQFncil|t`#^=&a@~ZlJ zj5}x!hVM2N16|$dSh336)E+ng0GO&m+48h1vAf>-+G6zY>}2OTX6}AynmiHpt<9h) z!uqG5XPff!t9iyWw9K52#!(}%Y2ho_^ukNYXZE&tVEf`_-rv*=X~dYh`=NRFY3O)k zHP$}<3);g-^Dh58%9*2X?zWKtnE+BeJOC=q%#h(sCn9A7zGp_d0%7p z<7(f{ul8WD->+^i4iFx=a$UxRjLMbrKtpQ*BWKs6zPYMg;amD@nO5h=m0H6TU@9x4 z%!yH%0T~jr@dx_txcZ4sl{rlytb4ADZTvQltirGf)#zB;AJpJ1y1ktLiaq@ zUb*p#lrbkWadH{IE^D?>R$pgWeR0+u!Pl3%7L$sCQ8169C<8sJGBuQTg9+YuS4rh7 zaCD2OirBs(W;;{LfGHy-J62P8cndoMKBzM1vqajCP~0d?su1IZ_wSHAC!6i7EB^=x z`#0#U>>(+ZO=_o?gjroXXXRvJ~qjv(XQWBa;{ZIEF5!r7!HUSt(u$GXa$ zeCHjd{UV*|#F6!+R?w3ZXTA8vSab3D@_1pb%l4&=IInap(H1c48HCvbeiSM5DVNb61=4|p0Ze`ZU^s(NRV=ILC)i%H zzXsX9(AmBqU#U(3Wx8Pe7Ku|H_oOG zSTQr$&4=9aiiEOKHY`(2#EKS&T^f1pUU!~pRJJdRZ!DjNr^Jj@#T#+85kZksiO&~# zlc%6@##>_*R&{`7T`aoHPZD`LXUb}hN}huNb9UkLCswE>mA#TNt=i}W@>wN5ha8|Z zMX)#C5-|m4F^UlxCUw+^^QFi|qm=Kr4KPt-Wop3y)urnn>~kC=tim89cTw6`Trr0i zq-=99e!!1CI`mL7e#vvT^5TO*ZoU-GlLY!giS7}F6mk$wKAh<&H(rgx{FQ~Pu3|nY ze2lol9YH~F_Pi-BTDDS|DxLC@{wx&9iegVHNYla%jY%x;bKd3-@yh2c%U&n0lcm6T zE8@2Cm%OlxJap-mn0WZXsB3A)nn#{Mp|%!# zz3<&o(xNi!Vdtm{I83xcTpLj?L4j0sz{VuVg=;c=*9M{6D^c#xbM3zvFIPu17li$@2Z%V$u{j3YI;h7gon|q)6 zDE5B;ncjN=z{)>Ag8M%58TPZd z(@UF;4Xss}`=QZz>iZjWRmnrgRP+9@eGmh^QK~=`*VDPtFMenoSzX%rO%ggq@vE^N zO@Q%>w1Os6?phIK!YN`I&9Aj{t|KuuI*;V@ruIYmJ)t zAcyhmwd1xy4D^!2U*jX(j`ahIvqLpH)(!;kYsZ=ak0}cn^tBHL6zS!~nj+!hZ*9bh z-s3o57Vr4Rn5LK_5Z}biWcG?mVKU)$Y2zw;QicpEiUOh>28&l}9$?lLgfhbf z+Dou~W&0adu}wP5$z@(L*>VChgNXv;lUyhXRQ!e(996b2vLL>uGOJ#fK|^82uj{Nq zR{0A-1nz2A1zPnH=SYRAL8NuG+*=nYbZGf$b>@vYBitOUEtD%3}K(Rrx9v zlH_7JwTfV&*h7GkD-|C;Ls>sxBFenqQt1)gzLLyKuTGylW}5rlkeJxH%+&x4AnQ~=Ld2t(*`8Ev69t&cA?1lsgcSH5N4gMf9R8rrx^&sT zrXsel(tZx}(%o5%6%p2uY9E(Yo^!TuvXN;+%4ZQTESKITO3OU9uc&yJ3i)J{hO*}y zpj1&n%0n&{zpf(zu{=s=`%)kx*z#hrHM(M+H)zTn%vA6x3Pg`-?SAikuw?v_H7eWJ zRbpo;B9iBZLdp};*}jF?0U{93lN_H|pCSs(1>Er#K%u;;Vgqbnx7~~)a3LkfWGu<@ zO^lhA$&#|n%aiZp`{j(cBV}fLavwV_#W}VB{B=RAMaXc>+INR4{MjBIMGBbw|6CMwB_O(#n z#BX4P6OS|B(%8NTDECa({7KnJBEeV(@MEonIU5$hXZn=A5stuev; zPAc1NhzTwo3{0ToT9dns#QJtr@+dW42NZ}JMj>xw6b?h%AVY!eY(S^|S+QD{{hr-EBW{nJ zSA-P589eTc7s&Y$X$2c5U$I;%E_8keG zami?oT+*@B3Wd4i*v9UuHQd?7>W(KKBfR`R9t%9Q{w5g4^7?{|W=o2#1kdA)=;v;| zRZYD?>o|*@ZN&R3iG2a`*TZDJ`#*H!_l0YIjx`TIfdenS2xAW14`<&02Rwhxf9EQ8 zs~SohBEzOkL|scW>PNI-$t`zb`5pJ+pvyjnJKlTI-+XS>jSZM{@%b3J&mP#mco|mQ zbwAtaJo5WrM@`dEYW9IIMgD$%S_g(bauEFn)J;U2zcE5H%?F20N)|b$~vlDln z_c3h#>#G2O$wwZJxu3ic{X0AH%zyp}+g@7&7+~B%bFt3_=isofe;(_ed=6b3H{gD- zc7NxI*!03nSbpc9(Xnz3M(w*N_PO9}9Qu{dqGo6#7XIW{|K9J-(3@m21|huhDWg(X z<0d2(YZ2k`Z>`74Kbw~Pe&2IP;?>`8M>Ifziu4G^SX4LiHy`YcN-Ds>^$hm-?^oSm zc}bS0_ys8lynvF~NChTxmBFDc0{0_ohKOtJ&r{rxqwGpEl-chXj$^+yNo|+B?!hYk zGZ?>q#bAK(TRy#-M!sS)&>N+dc8X$eu-BI=)YMzXBlL=2ItKkkr1CWdSsUdQ(${Wj z!(eZOqrN=O+kj7Ec2fbfPa2NdCk@A%r@L_LC9BZ2dC;jiEmxEZCDpDlezTdr@D*|3 z8)Zo)v|B;vlwfvmA3)RiYS=0ht_*hBVI&n^e4{7NO4h6VjKT<5Ba}2I<|iwxDwJ7< zoT-+f46K0dn<_Agy|1ijtXR=R*=%1T<4K5I1(G#z7=DU;F`(%57_N>2RA;y&$RG^G z`_kWq6(A{-FA7$4`q!b1Pm0o7Q%v~xy)uR@V@PJ8gWZX%$KHbRDs0*LDix>MY+n)% z0;IDC15C_H*uL_C3p0MR6?;+vmdE%l70ePJUjk~HN)+v$(pM;yd5v0)|hh#*h((R0Sgo1Bz6# zoh+t`-VocDD`NB;CLJ5x_)L{3eJO*ns;4l}olj7q(pP7!GA5;DObg{hmX||3KDrE4 z&w(Ukyp4*kLy4~VMb`5dQp~E%WnZb{D)h)>XNMWTA>|`iS}2NNs#GycK}V!?DiL;Q zNUqC`^Fpy6R3^Eo(TK0)`pyPnwyzv-No@G>`<*h6JytVGwlA^#o+J#yc_3C_4sl(g>g2i`Z4GPtzKN#qv)MUG1c#L0?3QD6pCQ0WW$>`LS zvFyA>zNA8;(_@rKC=uw_+$lyXxZIs+sen^i?NWg%;-6FHtuu0s$j_~CctNTTj|eE( zI8S%R0rwc`XdViTuOWqBQaEZ2Q`i|-@$Wh;SUo>a;zl7uc~^`Qycb+WyYsw6g7s;P z?vN7CFM^82^2TgXxd-s`wz_f;k#qv!d54Ezf>~ZgHgarouU9^I&kz*6k;Blx6(o6 zv!>nruH@iCnalQMGaMepdZmgsC|`%0=V=mb|NiLfVb)N%{aBt9aL0*+qna2mTgMn| zTm|%Yx{i_Iqt2xN2XFkI_NVKy=r_0EPp6%SeJ(f$dz^I|=707Q%zW3$c5&XpZ^4^>S_T1>z{rBJ65g5l0V*w-A+6j$N&8Mc>K%Xs%)R691$iQb`Vp+0zkuv zRt(>5O6fD6dJcd7DBrxCbi`q(X&Q=+&o0F6XMLFcZu7!daLyBVp=IW7a5=_vF1Y{z zaO>&kqixw6$?uljd{rzD-SfR?;E*q0itf!@amP6qp?yWV zK|JBmc>sVHul*%n{^d={&(}ZoJZgqCVz2j~iD}0liI;zQ6Q2L!&tR*oG3TS_W3amy z&;Rged~^Afw*vs4yz2XS`Io;=-|Ok;K?EG~lqg$KVS366Igcpy*TyeUt->+7h}cU4|=NUe}72@ zTv~A(%V5HlGJ3^~s;G1V+}bBQvFgzsn0$E2zPC_g@Rm=H#lv4)7f_zsvU)obHs=;< z+$J2G=!78Lf9y7+NuFkHIt4td0{`o+6gxaA6=UR<&o*D}yEyJpB zslwZ?oWM4Hw=M0(LtkHy&9C&Jud4_UFk#*hoPNVJ@061YB@;SjpJ|fG_$?p%yNY}> zzRso$Ah}QiQ`)Q-Uwu0rrFT$`ZIp9RVZ~sI3Ii;;rvu9$*nz2s55?34Losn)BU*Q> zQ#a5j&TqtN*G<6hDNNBSq8U?@0u{P`t-8-#4ODPu-p5pN6d^Fq3O1y4crXE^ z54T*;Q2I&*k8ljI9#cF}umPwWu9ER<6Sh;j@uzNFN`+xygU1@9vbRFgAD8W`^*`Rf zjk74DSKOw|Fk%Z4zT(~yY!hWTJQ!8+Ci|OIkPzi4wFA#ri1F*O#KijzA6JFeN!6$= z7pobgFvnf?Y#gU^*uKW-ipBKxe;eD^X!)xXW2MzJf^1*OyrdkqZ@`H`V_rJ_&6+S$ z1n&KHn3`-HwAtgJEpQevq=SPsjzzmazQoE}? z(^vWmrBxC)NGfKHD2@>CTRw-Rs8C4$tc=@|@oU|JB83@dICD9<-z1$4bhdAtH<=`k z@Uh5qC{fwI##j8t?c_!zcOhTqvm!1fLPf#$^_j{6B~qLZQn)|EVVx%IYLD?K+CzR% zoY~13zZvU<@D!-LeQcVMl;)C-a-FHL35vqE{Cv8elJTaYO&v>;Jlvhnnw0TNQ~|3q z=)}O4A50<6oE{ujvq_aK0n|1MDif6}Ta0V{=_yY$jaOKIXADkjgo@m-`wlDO41`Q8`{orme9CRkfw(XrPx+i~{49 zR9GqVT4NmMuMubjcY{E$a|<(ml>~8D8Na&ImeP-mKi|q?{KABkzIr~E#On}S)d)wg zfWprN9Ijk9Drlq1&W8&at-Jf$-XQIk?8NZGrS?Qd4e6Wi0(wMM9Gd}M; zB_^OVtF`$jvo4m3ZJ|t*jNkP8()$!lw&Rx6xOrIL~?npfQ-5=wR?>HBST=^M{-hXeLbklWMdEdi${dc!wu&)opCQrol zlaIsLdHVwZHa@!$Yae?GhkfmG%zn>l80_xBlUIEY6cL7u9gWTnoAP#kOPjnSThTDG z6fHGHkqWE#A4YzXHXt z!uSm;e&xbwC6Ls7_FMPXLALQbbZm97qRCbKn#2MIn0mxe)CiUIy`NiyO)qt`0AD}c zW+;A36YP+ZSqkhj8NcO!JB4*~Lu(bP>kRsP{8DTJVb}z}9d~Z*ca%N>rH5iILxCGl zN*fmk2+JPWfn^WuC~Z*JTa4MK9uwye!QSs`X6H3-{{~E+KLo3u>I_s`Qy7|wR2dSp zE~Vf`_Af$qkKhion5Cf2=f%Bs`A%eX%dN<+;|AH2T>8jRkD3GhI`6(advJz$qO>s zz8VuUQGytq!AZHs2MnltyKLYHV2T9#bw8J8z;#DcctT1({?I z3U-ag`1LAQHAb6PNunsUM8&w*Ebh@AU%l}&{oZB!rV8sE#&1sXr4b6a5aU->5C^Qk znBvz!WdJ3LF)LWV(jmpOs>^1~$(7JN#xE+Vz$U%_sS2bFDK-HZF{v6YQ>sx_Z5hK! zQzkRzl;I3W#-NP?1qB&wUzfS5GICwT8GF7R2ZzYdgVTO(c*Qe9iW~$LO>&dPiy={V z{EH~ZA;e|-YK&j8(VxWWm>E{|6*7E0q8tV-SbR?yzbbQ?y!i*a|B~&iF*TF;lqwG8 z!WC)Ce^2pSKHm@`A;5Ta9Z5*Fx#0>$ZMact)G`0#j(VR2N_>d;gl$V9VR4Yjjxe5zH-K| z%j|Sn#3U5TTK|*JXUzC@k6MD1u!B|UDts2V>$o8l_HyAnRs7~y@fgJ!#A%*L!vmS0N5~W+8aeGJ z_~R^6#V-WomqZ@rSDWySf2(fX>5Sj3^-5Qf7}_7z){{xFh` zv3q?|Aqs_AtmSvsWcz3mE7}WRm(OS?b)F*E!{m7D#u;xt&smQRIX@(ig5~pLQs#rQ zy||+qIkFpVT};NOa0hA~Yp{7W(ASr@@gH7){D*G*&N}lH+;jdXFztk+anR>KiT34h z;PEeg8^fkf!hxUu2&Npf08@@x;Qd|u@|Aevs_$aTQE$c8R~O@{Z~OpjAA1V>UHE>C z*lQLR-So%Y&o<@qmnApffrl^qs`vZW*)uTl@Pjeq)D!TQFI|ed))qYXy`KO8Hofo? zHofo?fD$Kn?7TT>-fb!hHPx7Y^08iGKVr{W0Duk8yr`Z_=Y~z5@)ijP_tTjCw!;7b zU7NP38@P*pa|?Dq?F5WDa6g>=_#Iew`@L9x`+ewGxf<;&R-t>#wu;;2_21u?{C~^L z=@_~9Y&4D?jk;k?n04l<-s?y1H5&l1?ulpB_uKf~Lhol|=k8y&s6Gr6-+Hk3`(aZi z0RTqryVt*Wd6kMD<_%9F$Rtw5Z-{APbC_yQ*4DAI53l}a8}>eXMDn^rJ~Iw|JBp|q zUhr76M1=LvcVgf7j$pqVKYs{TKC(k)noNAl5W#)zPwjXELq(~QrZUE_kzV;^DuYZ^ z@Y{_GyU{$gHu-+k9`&fIvl#3Z@K9rL_RZ5#*IbqS{Dog`!NTjeqIpWK_Ze-=`qIj< za)Hmpw+vNJCaKWW7G>THFKNKVpTqua>^>pYZ7bv{Ah)#An~>LvejI72&&0qWh~e3m9@ zW%34@Y+sH28&_HrQko=#JRo~A(BrjMvTw5bwasR3g&uI1No5>}vZpu{ zH;Iz`$W?~HXAXJ``Z)@|LiwF68L{Li>{W#`4`WPc#Jl);v62DqD0&%7 z+hP1B<1J;3uLbE;qp;WwSKi%}3R(GiiN#EmD&<4$I?rLuDxp(jssfp;Ws(DU{2n&DKU?EohV#4x6qO8( z;v~hPRRU`=vVq89{lalXa`L4_GUqBxow$=I_*`XV`S->c)HDFaK44%VWn&u=2jzI> zGJXq%Y$Uc9@4%VMaFyi-3U89Pm)6gXqv&MMv+sKtFv6izQ1LI*Va!BU+ zQjuB+F(RG!Gr9u`&-=mSnh7Y|gyTuFfnmhh5ZK(a-n`SO$~_V@AGwdFZc34)Q*y?y zhDu`bom?e4czm)Ztnzf3?9TYg=C?9m@|d|IUsC5QX`D{ZQ;e^MxN^^cf{l?nErsV# zR#^{|5rsMU!DJ}>GZdY93QJExsxO%2D?5FKeDHpo9K~xm?PiG#vm&PoSnt|;4e}M8 zT0Wt!XPOFRMyldBoQH?=SmEH8A&A!RugGsqNHL0h#pp_!r1`X&JQyG7Yg|#9+;4WWZ-<2^+;p093)G}rdErVaTXijh`5uaSWwc?WB(M! z?~t*hamIbW#|zi|957EC|sSWG|Z*yQ&w z{o>bn>Oa0;(dSjw)ndK4|H{7%PWhpeeqJZ8Cz9fk3w1fE7iX7*I-Yt z_p|yDEdYT1FFG&tT;kW^Us~A{FYWt@U1~H)G)q+nBO<%ArFr zYEOROHoe>(RJRTpQ-$p-`jTrOF|!VPz0-d#s%mYSdFJrv>-Li?J27Wv5p7{5{4 zJa0U*6FqH%sCU|N-iJqG#UrJ5bSqCM&2PlS`Hk%DUcIF)wbcu9@0>f}G96V|ampdu z@9b8Lo6~@nDYY0rr3Qa^{~Orwa!<-YvL!amKp$b#YrSmaHyR+;zw(Z*;w@n*ze!Rz zIU{ep>ET$}j%9AFc#SKW)Bcu$2+yFEq>B?I7*vvlWSvTXojIr| zDIG;o1_4nh-oo*YLaf0o1ZM>(b&CAVC{eO*uIK_ zi&a!J#c3qiK;bQEslZg97l(mFA-21W$$?)}fB_s5$1c@b zJzRlk1ap;jkPK*)b~zacp^U&1P>i|EO9ZU4vzX?R4H>gbg+iO0%dBa%7%`<9O=GHf z?RO|{l;bxQNoD)Gic*yU9#=pYUBw?|vVBPj)Si-GWd#S=zFPY+We;cI>TKUY`?<

nobr z@F5qt0u0$Gm7E}oB3Ns6XB;X!HdzabqJU*U>YkN8kB$P&nWs`wD17BSmbCmdpd2c{ zb)qD67^^JbD-v5E*BoHt`Jty^jGq@!IsK%>iAPh2%A?WPzT`6=gH8;NLXcO51(e%P z!jR@OF5_G}&Xmu;NU8pgxZ+JRej{mc6miRA>0E>Xf~_(vu5iXXm6htw28O55qkuH5 zbY6w((x&2IzfdgaF@6=+a^`f&1Pr3$$5$y(W30hQSYDEFOd*N;QO?^@fQ9xC;`>Uj z1C_c<%HxUZJuT)>SsNBC&y~t@_$+Cab;%Tb!fA~hVX>pE3m_z65MrVFg`v_igk*lM zY*Y|&ma^}7#K&7}<@}x}bLo85gdN~AHiMD^U5y3eu7|OT13rIpV{77k6v)qwU@(W+ z*OF%~4K0G3Sk4Bz%aDfgm9>#zmFfmHlEEHOkUAYmV{N<7X(diN2sk1o-N%Uj90))L zy3#k!@k2~h`CNpi%dT)Zge8ld`AW)!)!WO9hiv|3caow=VYrL^53&G?PO+fn z^C!=lC%`%{tz+=bU^h>2oli;(cHx{R<{Q!>!JC&wDON>VWW$(!^PMMR^qhV0)Hi;BovYVj=DSYDh&^ZG)f;|?7q0m^=3n-)ASd_D ze8tM%s=8Vq>s*9QQo z9@0?p=f;1(?ACkm&}Xj1m;?63NjF`G`ImnTs~&nBo$EG$vrxP6-1mNh=f3w7)D0ho zaR<-Elw%fP`bo!PpAVdc+NPm+aYU%@k1UxQ`0--pii8vy{* z-*Ft?_MN>F_TOMny18E6*nokqZtwjX$BgnuuHNvvsenx8c2GtD|IA1Uh`VlQ^(;WkQ@aY9?@S#MYMuKA4((P0nI_JH@f0GVZa5LEC0v&kg-%tXf5X5zsFlb3!t!J32qYY2v1sV64)QCh|LZ0DF#G8Z?MT>UJ~t)n{yVSdbc+44_+tv!63?Q2v<@Z(p^Pk3gBjpB7C;{6p8w)? zd|t_~iDq|lt~6!0)~~nk%)o7z9)pqTZhbhh{b@EvlaXvTOWM=bQ|4Kff!n?pFdMd}k%B&Rh(5iNfv zlGPePPi86W*m4dlHEl5+W3k1XP|6EV#ALmV!qhn+=a7s7C#0J9M?29hj{s3!(c*rS zf0e6sr1PQGF@|=ugbn&RTNGJ7;ta9zh^W4B@O?|^?U?}GZ*OLli-1K{Xv-j$B3u!`9_ON!UxPy4Y zh4XB1UYMcfz>N$%4n|kxV+3NQl34nSPEzVL?jDA=0i3=IHr!dA+abMp%l%%2Qv-%z zp|O{hn>b7W1%xCA*OT`SqZM63)?XB-VWAX!}5O9ze}oMcZ7y3i^{=6rdP zSwLR=&eCKgRyGm=s2evHAgl};JYpe60%5(TUa|93UNe5GpX!&1C#GsGE4l|=(Cqbz z(1-}9VT9RIqyG^^vxb3TGLTlZ=BzvFQN(E9{}9RR+G6Lku#_*y-x1~$H`%fJ#o$qa zcl?h1QYj{p$iB;$LzvuFAJrqdS^yOB--ZFXj|r-|y=i_jo~SMBT3`9Gis-0EL5Tyw zRQ_|vUf=7zIAZtps4&1nA#R+=*7H0PJ>1>L-?+m>(xE@)#Df5cHMb224uQ<#`SDNh z$L64(3NLVQbHE-;%gHD%T{TJLnMp}mc^?wYSL4?Ou0EpZ{&%cBGyVsUrruR0xC#Vj zAa#raw1}J_YlhQLXXZ#QiAg)Gap!Q>UCVZ$jPF0ZKmTTf%#JE(*>7SBaduK!VGn8` zfZ}BT{I7BjAP1Rl%r^4oTlO~u-NkGeTjD69t5Cjt^P)d=BxM)y2j8U4mh9T!izS?; z>90ZG9;kXFxE8@@dSBa{09FfF;Z#N*U|gdo(&HHCg#q0>C+jK0w3Umm0!35_j{nkv zyWcD*D|A7us#Da-3v$YMS1P2mGA%fNNq=8j0~>_#1c}pZUh2;2lLySb`tR_l+i0}J zy8+emm%cW5`h#=nMX13;yEd8SzptKov!{Q2X3}1WZET3)@()9Di_7=cUrSrO ze~J7?>_^|_C>xv7>7@W=iTK05D;5g0Df>S41XP=C@zQRAs>B0 zwkK7EDy#UBv0(L*pp8I#*lpJmE%v46UB*t_<|TtBs@_ZqbAxJ?tZ{N5XE z-a@pzx~`7L3PuW^JFS&0oci8l6yCEv(#8SnxBYcO=1j=cN2i(CXJjUgi51I}76bpy zZSrd&M(?{H8V(I>J{}G(fo-hf$7+qmE>j%JD31#0w|_Csv{h0X%dSn;7C1%S@jOw; zR#-xpJb#APet{6B4XoD#r6ly^h?5^qp%RKpo5NU7c|;M-6!~py0SPiyEk-4M4QC1{ zu#_gyK4|_)iDzwz4dk{;t4zB>ze+z^bt%-vTk^q{KGo~GGBt2Il&)EzI zN{8ox4KB$SY`{)QPs@8ejrDBSH-*`G=WeR2p@~b&-sScU+=X=86EE+K!!-r&^!8)5 zC=nT`Otu3FnnLP^YW-5sn$WPxD zH!xa~yIC~~8Uz_fPyyc5ecnmD`1(HE|3uJwLa&-VwgwWEmwXQ9j(v#4*oT;K{LayZ0`o10P`BCirB46-nZ)0E{NVpkB^|*EB8|x;1 zw>gczci4zD6Of=xA|+*%nv*NK^`qj>N1HQiJF0+pD4bA=fqX0Z{2Rmp1I15dK#n?~ z{&YcWho`p3loAToBmo|aY;W6{w&#%xh+7!6sx zsy6l=?ot2DLf3a=c9HxKCyFT;K5=a#ZttW4GiaB49*sU(+amv#(mW~SD7F8-ij?&`pWh`W(2 z1Fv?XVgx!@V=^X;!>@H7BYWC{tnqIG0xP}1tJRh_9{;+lD|(Nbo0BStf9s;fW$HxS z?=lmctCQ1;+`!qEPAo!%O73IQ~Wy%gMlem zHkT)iTg`hFf9C0o*o=*xrg)o3vDtL|XHvd}LDs|D#p-px`!dRzllR*{bTR>drgOJb zi?%)Ap8cY9xEHM`S#+hEa24mndC4Sp-2k|=TiYA|1dc&T4erBwgwUp^UivjZD!00e zl^(MQCI#)N_@AuC>(AK0#6y?!6?H}r`T zvzYn8<-pkMUK}~hr+f#P0&V+E#1<%Ao(nDEQi)I>GKF|J{hbql09~rD%ML+5;S+sNLA?(xApWj z=X&v+}|Pn^hV_kOka#PSG8_b7)d@t?Yu9+RNe9 z?|nmD{ypB)Z9JpTju`e}`Sv$A=v^yu&OkRg1i$60xDY-4M3EbXBQ1$o7iqrLY>uOz zglx*TO$_PDOg{Eo3hHq0&zqafS<#y=U?x&%m;GAHVS|E?QGM8@pF;L%H|3uO`%ABp`mJC&G&Hw zuQv<(?FnoHkVvl8AL;twi6GA;r(ubx>3_YOb3F0=)=aaJjoA#MpKDs{*nXGwX=n`d z;}1q?IoAPK)&%Le3rl%1bZ?ljg7WpSgHDtbo=f_3{roMoa^|`sjul=N^D#kvBYFRp zM3h4y7&cPuH}1Bz?WbDxCA6*o$56O^g16{LN+zl&Ql@5~8=^_tJ`mQsGZ&)jJ~W!ms`%nP@h{mO_|nCh7IpWVhG zt`{`FX*f@(Mi!LzzQ(||!pH=+TAX6C$>1Ldvs1|uvKZ*h zTc^C+mT#FS`U>va6!xkmMku^G_?F)k*`aazD_cB{-$N@?BtM;4T6Toj>6-riQX_Pd^qmqrI;e^f_=d}@&TM7JR!;)P)<_4=)$QlHkD1yj`Zz z-@`11(_-E6^1lX{(Gam+s=l7!7X|}_oc4)hr|sW=-Q(&!;82JYdS&}@?fPG$lHbpb z)LzXkz$Obp{SDB#zyGb%k^6(_YzK$MR!@q?$PA ze2D7oa<(~5=gJaNoV>*2jI@O9KX!FBlDx;s0{ibK01Km6kql8@pfncFa~#LNCnd26 zkxAqqFQ5Qcj0P4^j~@+|5?12Lcq% zFV*^j=UP^<@p+M%+<(Z1SFIEczh>u(vLYy#Md+C!um!*KMCRB}7wf;D;POrJtg!{d zF8h?V-t{Hm&g46BuHb7C-z)SEZz%sU;*?NX#QSwRLkeLEh98Z)1I6F)ur-yn}?gt9{cjfl*@W_qG{N=c_YXI^bqz$ zCXZUeP3)&SsiKmb<(!0#dz@!?Nvw+f+9)^HBjyinn3ty~A;il%G3j-A8E%yIALy1a zEw35+>Ky9xX5)w7YLx~rxYB*Ex}8RHHGU=H6Z>Ylfv4QUBD<8;B{Wl%+3&uGej!_J z(_8>^S;#C3=nsBBg~w9{NzqwFfUWE<4{7~x)maC-P980EsYtP!W7Vosbh{% zQ!S zF#3bbv|argJ4}`ULk3{Jhaw-QPQTe^q+-z6SGITd?Tm){n_O}68!VWbu+Fay9~Ap6 zjUq1yN^`5^lb8+a({>4%d>Y`?M)=j2;6Vt8_Edc5uUniY9Wf;POLr%O*C3|_qv!yyg_Ig@x*xqZVIS0nF`_NsF=BESuO zBWT}(xg-6ZrGKFS0zhMr7#8|x`V}%k{wk^`O^z87#qK2+3DMAZK4R=r{;sa0rTIjN zF+7o?&dBXvLwDw;Ba;KmO8kgwPkT@GaAz-_S-I~S-tsK-JO7&}!CX~OGhxhNWRS|PDiVRTSH5Vy9Sc!8Szbj`QF(fS_4_=$@*nl!R6sD%Q_p#E&jD9AX!>*+tU9+Mmdz{XSj66NWJjUmSmPWWGl1lNK|3{p0^dMc_8 zz@WU{2IwIcwT32C5i?iBwF|5dyFauetuV35yJDPpD^S=%9e^Ec~9TA9~ z(flvgja?QIm4rN}V{_ox$kA^iq6W4U<~;%c_n4fC&-le53#s?1pLn+!=$RbF6bcXO zb2wvnNX5NHIif zi64!-ar9sJjXV|J1VBVC);n^waMs#9zI+=Z`i8y3Aqi}Q0WLV*iAY1{qq`ba=ehI; zBVhC&0i+_NCdp&U8km|ZPl)kER&WL{92 zpg)qy&`q%>jJ~xw?Pho_w(wRh7ZF z5&&?!3xD-5dn0u88q+_86qhO+0R(Ed?#8xcSgn4qUYg%wFQsoMk zY<%eT^tJ3z_dd@>IKcOSr+Gf!y|h7H<3wXG;fCg}3%JT*ql=33bkU{GrPQ23ij}kQ zh2D4Qw2@hSZ2qt22fhs_zls?GQ&~acOvkKy@?g8FB|RxE%_9cCIT-C>6<3_PpYtms z`o`7oQDscP>NV{`aBB1Q<4PWXL`$CMus*JoH&kB~937`M_3}Gv(&U3eY;n6Qcw^L! z9jZtf?+siT37X`YXHj{&f3B*FI~WsBR}{Hg(26cKHQngDejw{nu!5YgXz9nDZ#6l} zsD6LSZmrOM@^K>CY%E5f>ZDmNeEf-&a-Dhyb;&R9)c&V}BF$0fw@DlSt(JTKXH`AF zKXd9uHbKnxma+d|#@+oEz~wtG=6y&e{wLn>RPyY)N#JS;iklX}g>g*w@c0=hz~6bx zjifQ-?-9B0`~TBi{cx@^|Wy|m}%%wSZGa(nkN zL!D_Z$mmmYVbT>4C`tt>^&VnSAw8&6R+$%`qk=o$_P(2RBFJ${ah+bcO$}w^fdfW} zX`(i=^wT0(uxl~?@1GxKWj-e75KDc*v7xiUDRzZx_U9zo@b0E=&!}rCD=O!G861iX zl#w>yVmd?r$Q6wFf=ff9HUC>v%s1Z4b-P&j!1|d1oV!2Myvy$7R2B>ZG=5v_yf!$q z>~b6D%HF&Ce3?y#D<0ZJ?#EjxzVCARr|#X|Hz-&c;=Z&og`M)?bbs;e>*Yy~EB3f& zdjuPp?x(>mj0ke4di)peG~LXB02-{`aXwZg5C*D{EvPQS<}s^{*gJgBhOp6<4sW;m zYe?%-Ax=s!nsVL_a#xm}E!1%Xx&Im{b&tA+E4QASXI+hRv3CMNAEUtVBX#?jaP3U@x5MAZ`qm$p&OhnVwzYY_b7nJ^s!e?em?2qOZ zo?=RCN3*{IkXSMQZ(=4*}hVT=Z{DdOn z^YXPGaI8Ls$sOEttg7kAH0i37I~gFTfdf|xJZF8Vz7u5K4Yg;9itwEEfB2RSw!Knb z-(+7_J$;Vrk7VK%R0wN0AQ*=ROL0LL?jK659{2^{rw%0iWy+Z!>`M?QYJ}N4Hs+6E zOw9C$SV7N>GD6307-vgk*l?2pOb< zmz@~vfZd~kkvHCUsmo=zuBH|$jF#jq_I9FA8&*ChoSJk~p4ois`i?uWHU7nc!%af+ zn}ryn!;9e*f#AX;56;&M%|@#QDgek_zp4^c)uXse>`9L7dk@GWHfg(!NB$q)*5#47 z&ZPXr6eqM9IT*d3vT;wgtqb`7EC6^lm6{2!v8SL&9ea=GrM2P0{5;9@#0>TY<4=L5 zNy<_5rQo$2V(!*7z3-jwCIb^Q-ESkKW~c6LWz*ttemrbVi~nrb+ejf!ykS9CK(D(0 z^lMxV+TV2UGY!`p`9&U7ZJH-S>MU4uNg+Kk0~2I763mQ+!`k$hsoZ)z1T?5W3RnzR zizj2^`*e$UR{z~>-h`KgQBuX79bWAnEkq=~Addl0c{5gkquXBXMTfobML3tLCNW|+ zUWLtjPv5uNfs5~bdT*w#duNZ7ZcV!3iY$V@O%&EMjt*BbZx?tBW9u`H+wl5vPwwkG z)KoMmk;7Dv41r9v3u0%yrx_PlnY`Ss-Va5z`J%q-$%xIJyHw8g1C;$B^oBPjjxPU^2v?x$1)e#+cJgZ7>aiXodimxaxp2~UXU zM+Wkm^{@^8K1R3pyyk}PB@nEPEgU;)XZ8!VzO7rnt0ws$KYOEcGPmDTQQh20hZL^5 zg)_VWg*gVMV%^fakc3Rl)U3~_b49mbwJr&hDA}TR6bS5+`ri(H@j99+w>>FZs`zHD zaXv@-LJ=)hcTS}g+S#D~qoSsDwa2MbEfqWVl|2<`@gvg-W^m8>UZEssyYFUQZ7Onv z`n4t=(P@Td#?WB8>IL`3P```U#-$2?dr%)m-wTLi7qsr1}l3&H$; z{~x4tyGA2*f)hwihk4@zYCPMY+8TjMid03wXvQy}4*ZK`&`gBcyQYat`Ae9hHYP0> zg$$+T1i((MApeAAfvzCg`J~mL$HZ9kyhEV+pjQ+~Jk4@)zopfk2K056c>9Qf>F|T5 z`u)9ZV5N;iYc^e&vaiW>jQ>H05Yd^M%|d#sCR?ebv_;G1wrFi1a>V<)7^rFWSolJ+(dbxLJ~zkBVKoGqp==t6t9l zB2+W(1f3Eps^9fK0+qe{)<8X8)CL{@{=eTcT zvSAxu7{)%LZ}(7yhukB!F+?aJGT%crri336omak;abA3db@zUH6S*mDgxetaAJ3xa zuCLz-smwaL=pWD4G;U7>cX#$C6{>^iP98Sn_Lpyrl)WDJfHp^wYPnZ&L9C*#&o3`S z$eto7JKzZSX)+U;h|c}H%PAtTl6Wh^MEmv4hk=d$alxPOUTro3A}&by8LEC z{(0c}hT6dIkJbHTy@|AV>mG)nDRzg)h&6W@a26(*Ygut4v3K_2;G3rhnxq50(7Il> z(dNp?MRVzwuQjKMs-MT;#hhn9K23+k2Dwpdv)VL+@N9GE)k-F-D4ypfZAx+&nV1?P zo4r4g@ss?GnO(;il({2wWe9Z0|7rX9-L2uEM&~iHi*E;BBSCG9$7_d4b5Vy{RPEv7 z@qqVf%8&qf7tnL~Ol6sB74iAU{2)~H<+TBdMcZSco=g#mgt^fYX-q@Hv~^>liw&n?N5|_wZ5`m zgzwpV5o0JoWpEi@DVXW8RR}9deB!A28g_Ass9f{F>|tnCHUXR%zU2^PodvYE zNugp;%t$B0B%fZgEp-(hB?R~c>QGDsv}!13fs0fzv-^}S%Pa=JD%!fNs*%OSyO?C0 z4fSbCbT2sfN@Ie693o1Xd(-~9I=8aO)9);dYfW_38OVMN-7{&O%fhdac`oS@BV>Ku97SZ$-<+xO%WGfJmfB&G4Xl1r! z3nd#?G+xt)WSJk1&*OW9=Z{WkH06*NZlfz<&g7xe50#8%0~>w)D}NObt0$6bNn?E6 z3?zLk8G#=~+7OUqG&gQ2h%+CE+%HeEFtmq%y;%Rw-?n+_2DiWadjXSiwFf>a2 zh-Siv!>A&d?_d=#Ah4FFVgNggT0tS5IhZD_q@3xN1ygn}3dfbDy2V~+K4t-6@S-T= z20*qzmbBW}1L2^cGV9OGO^1%PJ+#)+@F{Ssu}UCErR#Paj)TpLaz$oD^qYUpUnMC&S{v5L+Bgwxztw! z1UiRDWj8^#WWEMCFYX?R&jbPy>C|g}h&=rtO@>$yX=yuthySX{ocrt&ME+*O1u2)M z{;+X8F<5pzo@%%~r@2hq_yj(^pQz6J)Dn?qTpu0?3xXh#B_Fc2J1SsYMeTy)lJtL% zCojZIG+-y|tnldR`nkJ1&(`XJl-r}kL@-^@t;UieskHlEuvTm?u?P(f+EBsd5cb*8 zU-l^BsY(5&5MO%Kyfp6DCghKb>_75R36K^0pK7E zmPwYxVoyHa`im)}3%Fp@py!PFVF^<;`kiS;3T)T{kqiJJ>E(xq#B{zqNS|i}N~Nhr z-4?sdn;J{H3=71(s#>{brzhpnSqhlw(MLyIAb`6zU_ut-ZXkC4R6OUumEZCsM(cFc z3QxC}byM~I74x*~?zFd=puas3QKjAFq)n^>xFIaJy;?oTv_LRV00|Q%boS6hdt3HV zxaaIaqV;R(Cx%%Qml_DdCldvYf#vTbjz4m!+3a8at9v)_@)z+f++%?ZsU5I5S3hGe;is3d)v*9>kdmg!@sS=@T4Dsx1wqr9lmB~gSTw8 z8@Xx)3|W5sqSpru)Au2iN^XdXLb=m+@agT%bAcUuTWX;H$VtiDQ@V=v7AjGAWpy!w z*cm~(VY}z$^V!p(hx?ce(Tp+Z9uw!C3NYAl6P{YAzcU-Vr4A)vN$ zlTr26dvYsBY$ISz`NWrNza9|>{~rH@Euao78G&a08PrFCsXy{UL`#HeVC5*SYYvLM zj7KnVY>Em1t)ezl9fIV~kE0F^o^8KcctwOqr6DGqL8wXnkKQeAja(uOSagraOPE3xL0G zyF>_wCCb2r^^pm0=gI^TMW{jXZ(4cP&SK3E{qtCc28p;+vC=Yh`aDkH^}i@e38x#C ztpd7`kK;Jm8HQCays-v<^h=WyCsnmP_k+6IDkH%)PuSySeL(M?!WzsJHLT;JHSeyV zU=(hNM&;+w$>-VR8eA}6b<1YaPFUxcf2N`SisxvCuY3!r`ORmZEbA{!Oth=FE|?=D zuI~7AjzJnB0OCqbH+HK6`8Rp5_J+-to`5px^W-?=W{nD|6Wf zidFP=fs8~qy}p?or92B`iP%?dE|PvYEfSgCQkd|q(fmYy(V;#(6X^4YHslf?-6qPB9(ZyYo;c)c^dqJtA! zYpU4+B(3kwAjP09H)IO=>97E*-6wrwaWl-9m6G6G&ms84n}B=pIQ=TIVAQkhhA6Er z01MQY7n-r{Lk3`{E8pq2cC>49y5f)KuGm4V%G`1EUdM#CU$+)#oUGtEsB5vxfg*~L zFABSUzul#QEx1?jJjt^Epvc%6ltAVf`7b-VQ3_Blll9aF>|7~NB@JF@@%HLyxyi<| zzb~O_#GT|=0TMr_O4AkcirFSV7eeiAIS(>UgrC|S(oW2GIvzXD3Ff*Qks$Dc2-|oZ zl5=0ENLE?u(q=ckC|IcPMrF|6FTxc(;3<|d60IKx3{t3j_JT5SPJjbNE1kk21&^l6bV+FIB5;udWh}VE>%L=$E*%Q?%BHwpM1LV z38eda6UhgZshMIHGg4Yz|Cu$Or{y!I6-U$MO~B!z!-6E)K>Gbh_43gSzdKhmMyFFV00t$A~Zi{O`*j6m!vUVl(f4zqKv@PnLq(GX89HkTqE(}5n z8+5lWCTNNy+#mKV%)v@WhIe;lCTE3@kv3hsm^d#EHsO(d4ZKduu~HTS>rAo!4;?3& zsIpM`l5-VsMycD29b{(K9%fy)Ym$?4S2!O~$E{sdUY{J#T`*JvP zCh?k7$iSsb4T`?zf10RZCeWAY(BiLvCLm42U5iM{yzh z!cDJ4ACvI78w^tu-pG(w~w&V?aO>Cx9!UuSUx|)Hb0$5%~&{ORX8yc z_V&M?>b%!iQ6e*I zn%7!MJC{Wxam)C!l28+n@Gq^V`L(#OdFn?vDf{k6c+s< zpXbV`&LUmfBtp7zQ6gchO9;`vg=ebWQNst#Jb@Yym1)?^M^R?DnpH32XRVqh!&D10F+!$tj9Ph_G)vjEEHYq#Jr-{MI`fl zsaGCEPSW@UKERjJ_{83K7hUbK%Lr{%>H^mVZheqz+6m0HEeYs5o@m%)T6;TNA22g! z$S(MImH=cl^pPNfE1tCyjYEE#I|b@eQ;@1TH;CR>y*v213btgdky=Jle&C}PAvMIj z{QHnNfkZfoCgEzEUchlQ2>?0p?Q$&CTNWoCrCq8{?o5<4lj_15PKz8>ib45_SN ztFe*N$5Q6N;-bH0*8TH5frfy}8Fd3)(wMAcCaO=HwwPM+1l?!@-xTJ4o?xzCyv}6a znttR=H)tv;KT}Hw%-Nb7*sE+Xja!a1H;K$1FiK-BY`6+}9<(Qg(TpMUV z8zP^TV7{gEJgJG7=PnX6W(mFBsfM?bX`}}V@|jHG4^|l#w(F;9Cdy~hc<{{XUSm-+ zwajLwZ-+Xa#xXM8ml0Of=~TQ22p;$}I7-KGWQJuamoH3A9v@0PzqNL({XFq(s#{FP z6e!&HM^7ThlGCtbyHR;0eXCg{khsF%#~w*NUC+FxVaM`YN^T_L|68yj&NAAf_g!o3 zmQJCYh#XX8WM-@LWGx;W@)9TsQ`%}-XtC8*S5%FMkx?v6--4Oh6toyI7HC%Zt18g~ zttp->^;=XSkRDXMg4>ced7acVYyXr#r++DPm733PHt##zCYjI3=9J68%>uFCGebWB z39L2qBUvn-YxDjPW>@F+4%JLgZtFX>dX1#T$GZaYm4Bz|lUoywM1hvljkWKatwtH( z>B&|@+FSs>FYX)FTP?q1_NL*wON_4pD@7%t4Q5=OU3oL>OyRU@y16jv`&Lh(yQoHH zmf;se8ge$PWX41$HM3R( zN5VLEFGLbrx74*#sbUErXfinktwEfmHfuSY5T*({K1;hMM++5H0>;~E^vbX3-ah2N2q0BCB~2B@4K8%x zt~a{*G;ycrtvhvhz6Huj*F>)d!0uvauZrUwqt6v+lc|`$J~($JR+;mm9b9-X@Fj5y z&4^6v?-m*n#skS5C1v!s29t!SV=6BA4$P5TKymewKc4aR;SCJ^S5Whx$#vF<`18h9 z0`AD6#S#NwiV1<89tCpSd|@IXvyQWw9|kdAob$;&0h}%Yzhz%B9vK5QJY^Lq-!mG_ z97(R>x?R~HtOlEa91WB0=A^Uoy6DE&PYhYztz^n+?D(q}Z!JZ@*U_L6?8V2fKoUp* zU^p3`X7Xq1hWnE&;Y}FJClTNxaMPme1Y4y|)j7JrRFo>4gJTSV9vkRP6bELB?*s5S1G?EO68rw{0Ypt_>S&+&m zuP#7gl?IyfcD&5KS@*QMrYfu7wYt4JhD+9N!| zJGrj0Ml>;*y^Td5ZGw(m%VNV}DFmw~n%|st@TrlpQxpfI*JeXJ&H9Z3Rv*hL($i66 z2Krtt33D7+#D)W!SZdj>_?pDQ6%(MjudQ4w%>}n};|%r2)Kn~(?{;!?7)!*RS`X#_ zmCaiwFx#??zfJ1-n+Uj|m&xDsX+MZW7LQ*#+g8IHajlRm5-i-6$oc>l%s;+OPU@+g z4^OV1&Kb+i*_+Fv;w$w5ex;IRb6x3CY;^<{O6Dn51r2%$%@zoQx@|;xKo}wn6HeWW zSjbKi`M%t2MJ*P%&K%|zB@ixEjD5|&mo{$?Qi4|nu4d|MX0WiVYR@-s@|yo+UT~A( zV#yK|V6;HgNz@W$5-~^vL0Ox3M~N{HymNV;V?t|Y^ySs}bsrn+ zldH3c6uPYsK2$jBVa-&ek$mMcPXKhXZ^Zq`v^5&~O~2@TgWSR@2iE5OdtU^slDn=D z3?hF`opaEUo1C_P2;Ve1EgCK|QNO!zpi5~WjWMlD>SxmKkRzcfKQ@9JL=y%MPd zVcQdhbPR_Te2!eM_{ZW;n6WjP&XomnQi96+KdR7}H;x(T-Hyr>zaSDQdm~A&;X)#0 zQ0dQZe;-kl1}lG~a2<>En(nL%Lz}^xfraDNZB(*VU1mFP2{AXxRos3xh*-CuU5kTE z3Qjht`X?5)eX#mCP#X3KAS${;ipfrtRgB6NS*B~drN1IIHeTQ4v;Gp9kv??fB}z|V zE$1C6=p;>L_7_UmOEB<`70~Pf5^_Vz!mOPJ#lE!baIq|znOO_F753J9g}chUNOq@< zq-fKOXn1d+7LYmg%Q}gat|94|Y3S2xn@gItUK|ICtywnFbRuW9DUnypp9U)kNqU-_ zN|a-h)enYYYp{EEaau`!^k3eYlnXe(I#f%@t>%~LU!RI}JpNIbx!fLQPF0ZKgp^>L-t8*&6D$wqQ>jJ_@C+vU;hac zr)M1v5NZ#w&?(C#+Fji=OT;c={Hh{pL#LHUceFTlz+#xMz&1V&UUuCVaS(G4v<)%P z{Noa54>I$|2Y5`8?d$&g{f=w6L!0y`^HLY91){A7sz3gN%`#V)U?0#|TBcXv{_vA$ zT_%E{SQsQva#|W}ti6r~&$SV`NvCUxuUt((AscH??2iks6AP@e(>+0s!uBAks>(^F zo5L^iYT{tni%EikgJEFX(kUgz3bptXGOIzkBQ~{Ndr++gT^q<%_;AX5z?aZ1{RL%A zc^6)oq`4&pOMG6T4}IKjLN**YJfBf}<#BxsErcIMK|iM(XEKMH(}4@{JZZOe*`Hf; z>K;KaW4>E2|~VJ(U!Wv$?8z#G~o)y`tp+$mkW#or)W zz9Z2ml|#IP!h8xovsEleqdDjPs`})jOhTBw)3*g{@LP2sc>o|QbSRJ-t!=7Hk5v4&!Z8+Ll|3two>Dq zVf~ERTY{&MM`3Lnn7;0GxECqrw5CyAgtxDB>TZ$_+Pfgaty_G_fX{FeLI)SS>P6g(i6xl4-?Z4^39u^0 z_>s@$BJ@53?)%62_d&!4+=X5+A_Wq#tn~+N$!uGf2(f0c_t}fHm~~k z)zZ9<(%s@k-B4Lq@|*E%<@2uLzUf`brBBKvMDcM)qLySacfrhqw<=^}m^j!SsIhPQ zq6a58zhj4q{gkZf)1Wa{ub=wbX617nHXn4tL5y72j$b#N^o$1v-P?xtNa z&UU4eqq^#%RO)gBtb)~$wda$SHjh!OAQf|r-;}KU&Lg5E0Eir2afpJ9Eq6MBUu!VS z#i_ARPzg0U%hMO|g;DDWwEKLVD_w3e8@wJP*wq@p)=B!7xmsR$=lxZ*(h@-r^;;;J zLOw8j>6|LrC*@3`Ed{e~I*m?K%bcAXSzRVc+{$V6dg5$E=}KuKn;ij}kwoJ+!3PD^cB>{A)2&#N$ve<@d{mOx>{7+&2}IJRK0kIGU{W(hRGV}Dst>di z8ej9MV5*bEAlq~0Sa8mC4uL)2Nc|sKZynX<_Jj-91zKo}6+gHWv^W$f?mc~j>Wxcb42Tn5)?E4m)l>3LepLC67M_LYOvk~YtWu9uK zSy;6aa6TS2;@ur#rInPiuQp*|wlP~L|21}88zOIQrEx`%Q1?8Eu|5!tmlUJ#RW*P^ zV9Pyv1MWdWo!d__?o0+7S*vSD$t>81K?oM7wKGJ4kPaKGd&9dBOH=?4$QsCi@QtPp z8Y$WE|7-G(r(l}{N+p|+ldAj(n<&wEZvaDnA+!D5OkjZ6mO0Gg=B0RyACl50JK+>6>4-K?f8=oSFWpX_ib zmHM=9ISfxhnMf4WXydYH-6omnV4!KgjYvS+EUrjDe6DrfD%_c}O9~l(25z5m*>zgA z%m0C&UNLe=MCNHjyccg{$fiT9rYtBWUI=b>!+9O%hskXUYFL!Uc>~OC}|8=A|rHt<+!lqyF*eOIg|Y2?@jq0v*S!4m*$ioxYy@cjc1>Q z@edtCVU}c^@d6tO$;(Fs`z*tfeU3Sj_UYjh7`sSrDaPX5JuGVAE{df3zI-K5Dkf5( z)gIng|elPiG5w6$gN zg+8cfIp=*-PRS(6Ht?mcy_n@OGL1MQNz9DXhrD|LBN+r2`xVpCag3RqWCb zmk(ZvWg(6zkkBCI<899;BE60C@y)Us!w*O*uh>di=$Coc?JYts_N=E_k&B{k?9leo z+Xbunn*!NCS%*<8r!t$o?-_FrQww(97%3?#tcPXayN+CC8EK+yi0a3*<%#zbd(f#f zj9}HAXqPXgkzhqkmBdMG=Mg8(`$ouWkvdlR+OxtQO2vIK=9eIBs|8H{hdok7(o8mR zp9rcv?8`v(?lv|IU>fiUa)ZtFHT$w8=g*m6GeHO`$u?rAIP3_0sPWkui=3oBeXvi^ z^AA-Pr5)u~7=!A~qmN$SRwg4o5bjfBSP7$AOZG9I@{E-)u+@3FC9yjT#$VD|Z@+x( zq>hv*kgUakwfxm;JyxdNuLO_A_Z3jsBGS!lw5Lbbf2rX|WX& z(drcO2$dhmk)KG7Jwp4KX^}r!c(x|@JBwY4PFjslc4Xx+_qQwS_@t<_k>LNUz9xP5vbV28@%Zczybpg~pJ17QEL=*4E>yc}6|w2Y%RY7Wk@x*B9Ev{=jZ;Q@_>|npleRgv>rTp+ z*Z#=4D<1ueV9NU+oWNGq_5Md2b!m)fnN+n&~y3mEuA88VPiXq{Kd*7<$d6$ z|GoAlC5bIEW zJm&o$RoXBD0$4$7r7?}3mexA)$MM$mocuNSZwpDQ3ImX7JLGY`u4V#P{)i$c7XE?q zKx+|%nG|c#p!fT`*!>@{B}flnO0Mkf%6bV?WlWWM967=RHb)t(i>M>@Wz1R#s>ap6}8&KUxFbp5JT zzYs-aX{c$qQ^jy~|K5vjHuVcy3LPS=7u31|b_bV^XSS~6ENem^a52LQhTLh*uY1ss zk%%dIp09~@(5`<w= zsV9219DfEV`6^;J8}9Meskeceh^}e}eRZpC96CQ`0S4%`D^?<_FSzS{DdZX$>&J}U zHx@ee2VTJp6M_Vyj4Fv(^|MY^qp`%xqY0X2c+z6=ciKKO#hc$?tluLS>sW|V&o#l$ zFs`S_Qjr)iaVIj=FkJ@<+6&OgEMg+ULjn}!4cUr#)P6+l5h+i|WI!QiA?+X+q(n)oZwO(8sYv6NL3;w3LsD1 zvJ)J<;EakVQyY;eT1UR#$>dTwit}%>hjk-ETgRfH&M*ePl4!MF19xHjm6LyXAGohx zW}9g#gwdg|=>{7^@#ivEcvK%K%+jezXBd^l3WP`g690#M@%dFFC&~GItwWoGXh|n= ze{Wmkk1ucfYnmEwmoZoKfriI2$?IKNIgUi_Tk6RM?@`7ZlDBVDB{Xt9U5`3#?BuBcTm2JE!ir2$U?(Rn_@!OV zj?1kjv(6$SO3NSe9qnA0AJ654Yys#k4&RYROm5HL1?_XeVRwyT9d8H66$hexKUx}w zUh~$35Ems$HR4Su)N@dkC8(@AUe%jtZrF+(JHHA+&0yoG=iXlGU9t#DuC#XyH zH8?A_R+Ga5*y+*po za?r+_*4n0-b1)%3u2~l+HTyQ5-%cYCW6_xzYu4+oOx4(BfnF1G^~>1_N8wk@ToZ+u z#~;_smct(VEn=L$1&-CZeXPu?b;9Y(ee!Kpw%(3XY9g2;Qzgxp6uNoyRAu2*o5dub z)OzYX7WbF(!BVz*L=#Hw(y1@Un)|1lbHvtHC5!|@RMau$uO=Te%mL;k56AW09(!a* z#iULROIyX(v9d^55g)N}0yLrc&{}#j0)mZfk7$K--I)RJ*+ar>NoLYtNv-T;_jK(L zumFo#dLg1$S+x4|kDME&`*5ezL(`=rX^1V_pmM^^)Gzvc^Crcjwl?R6fb@Vhm=UN!)0pi@?B0d*P)}d`O6Yl>0vEL?o0cMTVG2ib|oymUOzSPzF<;T zR(+!(`dLYOmS7!)o{=qkf5coYepU=+va$o%jib_>5Hu;N-jzr(miD+~8$d!7GTk`} z44jCqjbVWccu`(@gZGY`{pQX>7q6OED@(*`vevop#0FL_mG#_SV0ho1@{5~$v%>T4 z{g1fd4>M$9RuaOZRd-upOn9YcRecBo%_I>**UChKdIF@K7nP2cCcP6CfUQyfk@}v) z4IR?O%b25^CkTJyvUO|>s3myUgla;DzTf2_AR#R5X{tYBVxEi3PPHXMjS_J6g-z@f zKy31FR$Yti4A^N=)BHk+6%g}RAYBoghK?@ksT`)r6H`cl0v?&zE5W?fYO2qJXJG^_ zJ}ZK|8ztZhykyiE-Rc9~56g5<%^AHhcR#ME6;Sz+QruJ!%{LPfx^P1&Kg*JzmRqs3 za);Iy&Rh#03xU!2)(bryX0BvKIB!O9*4&>_nzIWraVyp@)Mh@+T)v3K$x9U1oGR!S zvi6gRJ85L=hvqL(c4!g3Lq688G$(3t6S%SDKwI=0qq9o2x`xVI*pe?t{NXNSnu7`6 zlo!S&e^e`A$QnwlLCf3248iZer)y_k?|r}2M{(q(JvAgOhv^r!m&FFHfi>+LetS?+ zsKl<@3Z@gUA?m9ARjFnt$GJv*TdfN-xHO$t2Eo=mtTS1JSF_aLO8@X@hwozyr04fC z#NTyfS~zDxy%)t{+m_X|F}v`?Z&_LcX9QfV`b|qLU|pvS`*#&EZaQ$vVG&dF{E^49 zPq~u%m2^OW69X}`cA5~{9awI!T%kXD>X$Su=b%Mb5#0@S7-O8vo%c>V@kGg|&py{$l*`@%5?YbLBAL|vPFWj1E*A;} zU;gn_PSJ0iJU8v=S(L~oB>nz;AQ7@*H60tb31_opQSVy6Huj{Iw{P}wiK>U2Wa*f; z2y937NCfDK&j9T;%TX*?Ow!O+#jGx%{)qz7Fm}2uBZZ(^m&ph;T+l%TAtSz4uqyRS011Hldm<-5O zveC;O$HD{nnMdb@5x%4A%YAHUPJ-f1y6j)>_2h!CJvbyZ$Wlt7mu&q`$UZk?u}aeY zx*shzi3x8&S$5RWmMSiy!5EC4+hRkGurjM83n ztGS`2X~acp4|B7oDxf#Yc8sQ4_(o$i=o)cjeW3{T{=U=O@QP}~&L;Dodd+<^=x8)- zR*S#p%s3HMj36y;LqESaf`8PlDbBq-<*wd0wMFGK6?hiHtjQe-!Yh9B+`lo9>T=3$BiX`S8Ksk7@Ci8 zn$w_-VUp^uuBK~J1UYr`kNS_dw3*?uyfKQyz<4o&V9F*j|C^b}F4-rEW9 z{W*=zkaPqDp8Sv^-)B{0s*6Kv@MFb6^rjH5z8S;W&@%_7b8TQkImZ`GzP-A4TRkG! z3`p~Ei@WzM^i{SoWhT;Mfbf-Pya4=xA}a%hwenF?-now#%_UrLp#c95Hs2>M|&@-Oh+GAN$%IK zkAxGwa8OzWq*GQVVlUKC0ME$qJDmpBJtOH6wTzV`M?;YulB9VBF{@8(5%XRp_ zdfEJjW=O#~D(pSx*g`WhV0z&Jlc*-+KWG3GnoBCa!cMk4b~TP^bE^j^lH5_v0X3)6 zW$N}=mM(%YI4dG2QvGlSS=*RD~;M!7jxlckxQ@nwk>d!X}@}?8s4=|DhNKaP;REDao9e7$4NXd442O1}O`0@4_n^(r1*xd|x*p2|pMAN!Rkvg7&g^aj$! z|L7d~s?~-hh4FpDKmeCmUbzpall$8>Zc#9{T=>VTbQ!c*0(qSD@oNYi|HGL-j_7c+ z_(3BUcNEf`dmf$^JWOh-p;&14>dV3{MZ$pZ$YmNfmqHWQ-Q`hAe+3Ua65MloJWY@r zBv7BnAN=eEL5xijbx!4ffHW6FXjI zH4g76{%o*T4l6nkbz~P_5U0O5x+JMR^cY(x#rVWZo+Ck!OOZ4}y4Q@G)-xMaQcQ7Y zUiyva(Vt5#Cf}~l>YAE9B&=zPzc-8wA}9P@CEeun#`pAMuaSz!V_dYfwoR{(EJ1X? zrz110gASt0k}Sj6$+)vU;WpA6ZZP17)~~a-9u5fN)Mp*sMuHq2o^e3WO&blUgUKJ4 zd({6hEZL*OBHkYsO~g#Rx2sEwbiYlh@SD_Zul@J*j~Foq`iv%ebWQxFR{xn20o?Yn zLq%(iIWL=St(n%SXJ%=N_*6#o_T|v-e3@Ri-nh^X3B%*y)wCIhDVb&6o!>R?9<}sO z*Pr+-OIKyH;ni3@UEd-W`Tevz&@4_G(mTEt8x;!e6%HQXw@vsv}+1|5GdBY{-# zt@&%xIP`9QiIrZI&7y|5mYe%V_8G5;9UH<|qxWnae;330o{iNW?K)o#K1J@{9vxFd zu9YRDY!Htxe zhWRQ#6BZx*?#O1Jt-%m+O3GZWrke>*8|j#(?Hc7O7I>f!!MDrS+rcx(Rk0BHa=Ogs zC}QYD*{`y!9S)${LkDq(u}HBZQV;M%RlTN@+|TKLA@a@Fq}lh%v4B3EHPg$>d8Wn6 z;}ItJN=0+)HAh=H!S`c>a`_nwlaK4Zb5AiU;|d@1^TfoqsyQ0?OVYQzp{R9ymZ^U@ zQTR-wLEsl^HWc0< zf;rDmk@CCr*ToQ!z_NjIKf=fH`LjXnY>IIYK}VJQkjwULiNV^X>`f0Fzs4UAIJY(` zDE4z-K}^L}dmqt@mx2206;Y-v#64BdQ;7s$>u$>{ccZw;-@g-N^$Sf*Dz9>%lLP5- z*33m9CvKXFd)i+JldyC2_u*Qc`V(L7lg5S&3{7#eKZ$S3=|GV5-g{|Nq-VWa#nf`e z*e+Z~=qDvphUpb~>h#<(F|c$+Qy4HnQ{KKOyF}H16ugYFas-NZd_ivguktsk(ga z;`%H%{WeOd9^I!Y{!3#v5WsZNgVP$ODN1!5Z9B+7o%I+eIZr8|FQIrQ?$xBIYuP8C zl^TrZw|U}3;z=NAWwCGg;{{~|ckcwU?ayVs#B}rK1-W3OEQQC`A+Q|Qc4@k3V_2{( z+xrv!@o>9!z=hI`kaRC$!^4_MTOLD9?ma^)9^x1CkqF<=_|=uYp-^gRJcI{@v&3=7 zNcuJY_0HZ%6OJXVO!Bisu|y@S0lKYw>N2#3pE;J72K}&@tA0ia;yocX&}_G(H`)kq zC?C+Fu$w{}m-rmNvW#Y_QyER--EF$LFpjU-8e6B@3~K@!mrv!B40OhMSg<)&TzESK zJ>u>EsI*3NiKZSni^DgfX}23IUdd8yDx~peK{?5b<#}qkk3D$9`lj_&E2EHF4>egR zHdC9iMnZpdOMRc~uKV@DB@2(lE0`XuN`#>x9PbcloRq-zg~qQjh9GA^V3bQtJ&6So z{+JFOMeCP6Ne&ZaabZBEyI&F=+Fy>ZZ|C(5e{{^iqDhi&UgISIoT6;qgG1;FdMLK&Nv4o_Je*Mhtpi?D!NB)DJ?Q2~c^B%9>)h_fE|ll)@5 z_CviHO2_I!G0QTZnNk1}IEJ;({yyRRsSr^`qTlvlTxm;pi`y@E?j(kS)c4}mc1Lqc zSfvXIg0L0Q!WHrA4@>1#qfJZv;`mUW5K_j0;T85Q%6UA1f5u-ASjI|19jgZylZ-9t zFQrH1X&T=Ux^HDC;#T{9dH*)Fj4Krh;Ul)k#79-N3MgF)OZR*X@j+=b5)i&iHJQ#x zL={3k4ea`=LoIuWAZdyiIt~4FPT+n*X5)#k<~DTmYc^=O@8)BFFb}k&ITk5_tnHQ97&dTm+dLracbk5@ z+kr8~%bc#5V%KYt8N7TN!hKn zN?t&i26Kz*rml&wKyMsRJy@VP(tdZ}O3cpn)C%oAM(a#^I1aB0auRKT%-DYWs0^V< z%(Yul>8u_dM9MlCLtj;rVj*U-=@hEbGQc6)@RRD3RiyLParIVLd-=~XT*OUe7ht3Z za)UK#$R;$Q6F6;)#nA>}VV-bLVVK?-i+{!K;m;NKC%TCn-V5$4Skb9Da#0_A&&&8I z<}@b0zl6e$8Cz7Z(A<10Ra0l4(#ao~UnIn%2=J;tJ)Yo7M{fhJ8VNdOF-qFsckR`x zWf5F|JoDpK%msxT z>gp%X>>m05;Q}1R^E^eI&~j3^?Yak$504y1lz*7gA<`&s`@U01?`KChT7&#YjqQ~M zo~=9=_#IL~kBZM77MXFtW~R)`^wYBZm9>C^KphCX1zy`by0i7LSVv4X7VX!v-%F_q zWH%>HTQ17^YRq7M{0WPYixH+yXXSnut+=SYS!BjpE;>v>WZo1RwEq2C3g3N_Zz)Ex zO7$DhKr1?U)ijM50YT;Vr~@L1Od*A~wW~{w-_Hr;h@<^KcU9Jwe3*G(j8EMzZcJ08 zQVp2IU()}U>|(w0*|f#$&T5gN=K?~czlibWR~VzyT(<2ww?}i#06d;g(L(CtZ-D}U z&BJPzJunJKmUIWL`>VRG8pAlNyd0{#@RBAjk7ISc?j%@uvCNS^`R7e~&HVl#-G_Pi zH%t$BgOX0>K;IAv@sX@AYZM%NNW&?Ryu}%EY~f`S~Xpk88}f zpjslHHP*UBwcS=@RG+%_bn}SETI=n*(`(vbR?A2It+)7LAK6_VIamLzixTrm58#T5*$rP;a0I-l=A?QjB3IOi-c zlddoI*=G(TKAOVUjZY~4Jo>0)X`xi_uT1}t5zB?NdDR@MemCQO>ok1(G5kh zv0pI(+lL;L?-AT^;ni)V2DM>`{1kbQLXl5=xBKl=tUeRd;AGW9g{nba^%VMGTLoX4 zP2t4B7)-6@!u>X%qngz#QbRj&$xNqdVPM7Y!RpDX=5n)JK0&yT?_=!n_l|4sB{$@y z5hJ0;&WkjZotgp93(dR|S`*qh#We5Cj}bX#st8<8fuv*x&y9y!&)qJDU;6xeMc3tn z&EzJsY2ON#$lj6)YKf>|>aoMgYjl;i33;0H19cIvw0$4N71Yt_^x9hiJ&DgM$a(ph z5@N%}LtHDVR(67=ipp~^5q)*cGiivavA4GUN4$-mb`^Wz3Ws~5f6gS%h?S4R=uH=IU)4Eq#|wQoBWV!aC(JBz9VijbR9NjnuC0=Z}SupCqKT6^e5h zZ024^c`@r0VNT{x1q*_qX&5XdT6PV@BJ~GQ@-P9Hsn6_S2c)3rcAFS-UMg ztqcnZ$VfVZKR4nNtoZ?)o*4lKA?L-cm+38Vd~t*fGw8v zR+LE5m;IwYUEs4CbJT|~2FSz70|%*gyNL}JsSK5jdOdJE#I1!9$KUU`(gZQ3UVEp* z1*a?8w8XIVy~rDa$X}o)7kIdwcIDU498Bo;61vGLfB*{^d{4Ibf;aS9-rJk4;xfR^ z^J>3{=ml}hgB@dKeXo-F;2}-IT#cIOQ_-E@x_kM!LN#Gp8inN*>NpN9tz{(_bXIJC zkO=OoR%^8=oekO8@S18$bd!>RwEVLYD}rPQ==4V-7U7=gkBn{2iv&SrGU3FPL}kBq zT_kb`9}-Y*-xHT@ylqufuHUrR@d0(XrB$`F|d41U9!C(AY-_tM& z%ZweHy&DB+nB01UKWGu$4PO}d5f7HnSB5)pUXEqNj&Us&g8jk}D?0-2Uy#EE7cgex zmS^pDqo>-Av|U^a)&%Q7s2iOxSKN0JtU1dkt_>@o=`L4gwol&;lt^)tve^RqM~yl* z(NOV2>+W!yfynW4m;zy6R2Cnoq`t3QAJ=2m7?YM>-YaIksFZ)9()`+d-R|Uk7KOx| zMdYwHHA7a%etzmcd)-OIE|7Nd5lqB<#z@>L-~IwLP(9W;I$&a8VKx*y69klHxhf|c zPk|awe7>i3ayk(%=C78*@-Y4fi~D(7*bYa`4{cV6N8|YdhAc{AgCEf#GRvp=Dc^4t zRhC$>ZDzKV%>>5|2bs{jCdzI3J%0wm&qlH2H}dHiR-_%0zFhlmnUwn^3z*H8K0q23y^UQUH!;p_**gm(;=i#q>BNuHXW}EAHL^n&Z=BZAQa7-U_1^KC z|1lj4XHv2^dfe?}+kyjvWF7st;x1#Ex*p^Zs-3iUat>-K2NOnTmTd1K)n zL-*7nq|@nO5>)I^i8jd97b3=>X~-b^!nVMO7&`%vIWvhHRHs(fC&%wm_$v8PUq>If z$i*2cIZ*h%m??Tj65+)hFC`_u+fAa1%8`4T^aC6gjo_*hB(qlObwAIS3oLN{IiPa@02aIEgj+)3Re zQ_GTBFe&ep`&=0DKC35%8z#NTYOcA#{)Ek9unC6HWI_}0>;t7;1Ex};fYFU;Hs(5M zlvX^20#C|!3RN+2=*CvsXmm}+PMH`xU`mLXKsJKS*;QwQdnkYJs@sx-y8Q_(70b@W(k z_6<}V0rB`O=TT%?=|5(_yT@GDQUfc=bjj!tQeIFid!<7h}Hd-KzTbyjGqncu!=FMa}Y`6m0)Ml3Q%k_7Qa;R z54&ac{jjJy#0{6z>=LrnwQ@6yH{Z|JJ(~!xNfEc!$e~9>MGr@SGof7(_5_w_&)9MHEc$I|YLDn>(= zh|i!!u_@c&W4Z9|CzE>Dha1}u-ijIaPOtU2#lK?GquLtXc|axfZtf^ds{U42lKS7B zzr$%FjdZa_bl*s;X^9kN(iGXQ4kDNk$}UFuTKbz_^b`)mva8C9zNG7{{e#ph1LvaD zJn$I_N6@x2+Qaw;-?9XIj(F_?uk#P^nY^x{TaB|j$Fd3kvHZOIbxjM`hWy~!>I%?YJFP0wul{>6oQ%~1 zJoqGm@4&QoO_{hYWxa-zfjCG{-C}*64fN!i)`qRb8Ho-sbd^^H_*F`Nkp&YPkAW#6 zhw-cv9s8X++U9u&VScSu2ZcYC2CqW(WzP_gGI#o zDKcV5PYCqnehng}ADYjcb2?#;=K@LkBKd?F?S}~rWx?ZdGFC6FqaUXEi9Rhp$b7`1 z75kE^Ys4GrXrj>;W+lx^#GNG48UA6#DL+30dv87CZIre0BTMtV{e;~j7FdjpCcBwI zIVnq7C~=vhTs$8LQ!hixj5DS#BXo<)NT5q2$C2JCes|k1%dMQECxWGYIs$nJV@wev z>9Y)Shn=uwAS#Cjp42WK#m;_L{~ z$Nvx*csx-V#W%pEvhId&=DkL2?@=av^C}7$?5^bGg&zl5miy{ z!z4Bn9QAOy09S4M$DqKcmxQPYx+0U61Grj;(7-uEmc{!CQqXUgqR|@aEEm0BoK^+@ z=v>#)h@D&>^edp3j0Dhp-pYQHn0i>obTo-E4yJ$JFZnpMc3yv6_SEyfKk;0vUD|3A zHsD^z`Zz8A&s4bDnnLz!Q1mAM%=7nuz!6dC`LA8L1f35k_hrns=l3vmwMU#AOCrc0 zHD)B{?bH&LlfZ<-bEn5+&DP?}t0kqtTNZdt{^%#2#K|ar4zuuALEMsC?nGgf>=d|_ ziv6m_>)G3adM(e^gYs!L4eKKw+Z9M~2j!VEmHBwnGb^{&83s`L;9n%$A(*OpdclB1E2;A)urpFjosadfIswx;fK%JnGF=_M>cw&$}l1;8b`a$tOG-cJZOt zS!Nq25!o+;qAwb8W8Tse|Hf0?xXU`rhp^w8(8DvkrdMJf1+jNb8eY~;6mRyQ%MJR+ z%v=m)hwzJ18g~G6CG_-lk6)@B3bL?t9pMbhRlxd#ZTkyLm}YCTzFOMsc;Z&GfPs5RX(kMY`*!mZQXKQ)OV}Odhkp$iv{brNd&R0d;Pg@Rne)0aVMJ- zF^e&4^_&r4q>L0y2aVd}?KzlJD2GX5LzFAC=90Sg2HsSApKnQr9C`0LHOT~@24_dF z^6QK>>#Sw)46MT}f;Bfk_nhsbYqP@eQnA`6$>Ojk5`fZ8m~a%o4C3sk&n^d%>$bUF zvfKj%QYr3Yb=nStu<^$J%ciFxRt#e4Bd5zh<3602W~y6??md&I4Xs{~7c*#=a;t21 zoAIp9>MMK-c04K>(A*E&7~}^(d|&9r6M#~5Aej@Rne84EbkhjbzvF(Khtd^L=eTM| z_ARc38g6C(jw6qIRuX&In`{5u5)v=ST$)~w)}0lzr*E$;lCG|0VzQX!g0Ehx@uJq)|yVdr33++|}-K?^1i5vNZ+sTloz zBc1gQe6fM`5XH8bN_Fo>l0I4ZyQ)cT_o|5YgW}I&Fvy8Z&t)Ijp}r@yn9l_X3`F(`_35CZi-?=6 z!X5#+RKOs;N+@F~SjJbA391xL^z#r2<|y_UgmJR+ zOhIt4{94VDzHOr4{AN|x>cQllkvig-_J*2%cT%q|1u{n9a}_uiIugGKUX;C@jjT&~ z>7MfpXSrULpyiv1fkqd@bRWAKVH{U$wnSJF3!IxykLwffz%S5C66>gkqD{KJy z!w#^>H|#FyqiN&(5i5*XR8++>TRkaA$9ussnz&#dz^-Mavle0Xfw7l3EptS+Zw|lY zeB>%9X7kWq+d%BnSOJ|NyvU-n$$;ovac5RikypYRCtq+J^HZ2m7};0gZb!nt|N2ME zJ-j1bTo6a?z~i^(R8|10X(tPW6xY^EOAB+GuKCjT?o%2PQOX)zvQJbzb-{5sf-$ov4Yc{%!2# zYiTT_X(N`;FW%zcA@G0)Bw^>Gb~8XzMxY=2`s<05Q2hem-@iZ9NEKv%jw^;tB@raR zuuF1;)1xI?;yF`EaFopYx9N$lyE+-}MMWJdvCfbAu?jwyAhZ40`_#kQe5%<vCA{ zm+Hmi31LxAUhSVMa&n~Pot|qMa*oM@coy5Jj>~UgDwX99qVt}!e)1>uy*!2u4*u0- zA5^UQ2J+DsdKoK6r+M12_37p~Mf**P&l(3xD!bO0SQ5U%-SB^zHJB?ToB}ymv>{q* z_8la)O*`Fere;q6nokMQ7X!KG0(HDw^=JT2H*X!g&P+G^(8!vt_Ev==4hJYoG0`5D zIB%3pr7T5Q_^BNrG;u7XKWA=NbZ)2=!rT?sp&VW5H9N=ad(jer+Vt4I{@lxN0xcke z=d!^}=I^)=+r;Z-PG3v@vFau#Bz!+5$bmpAp%-a=MPkG^SDK0klSl+e6h3;$q# zaQYE=Hs_(aXT41SyYbp8VcZUu&3XIe>z z0)68I!^nA9PPkX}Bb4}CGU$!qC43X6Cm&sT^qk_J-NOirTZ;ks4@tSBOVRYLL%*dPVie=PuI7w!9)ZdS1^NQpX%mjI_Q)b zUuvEuWV-&P=zit+>?yFox~T<3i{=w{4LgtFNOyjm)b~)L_d*{I+vrrBggHFK5V_9q z^GXD2(kwS@rhRi5y^9{bEcWH!{eh0o$}2gDJAz%CUCdzDODrQGPi5ny%uzc?tOTCp zz&cSCb{BbndG*e-R&Iddn?6yHB9|uHx2^d+TfZ=r_=o;8>Rei|>$cbEH+ua}2U&I(b4OtknZ%G$1S zUkw^(u+o6v#+ZLW;B%o2?JPSyV4&Is3+}s_)T2TU(LDVHKhXEV>ao6)@{?a(I>vhRl}`Q4#T*&@2z4wO(4_n8=`UmD^)XxQ16W**~-W}IX&mOVsIuAl^s6by=+&O7>q?_^@5!2f2fH#!Zxevu zNuwMlgXps%o|Nm0rT1O*q6osVrkgWO;+>?b?YhqBs0;^l zPuO@$*o3scpR31;BpL(+wf^)X+z&~L<|DaR@BF&da{FVb3(q{o^Y+^1ID06HfjQ%l zUw!eR{d!IERZqhDpc0yWNa&vMLfT!XuoRX4D6=Z59UpDT#1 z>roN+Q%<)NRaFASbvo)eT9(UvgKBfPNffPnPM3IR*|p(mJ7`6L>^>{f3p1n3v_&>dbs)idy>pHE;}QyGUn$ zup+_p0L1)*4EiF0l%MT2zDZ@L5<`PIaH{|%PYx!H7KNC^ttit^+woBXz z7_lc*Z5GcxN&c0row#LQxQm#iI4E&VOTg3UIVP3J4yWjO-Qch!uE-}Lx4j%czo$BS zdbInnA%`Gcya)gLQO#X)9T+y^$e5l6CnYCm^IA^~vEtx9@bcFz(uz7yOL40F@#Evd z^|08OQP{KpefPlzG;3*=^ZhI7`@a7E;X=(K)xVBiqveD^3v+XSLL#CdgpAZ8u#Cvh z=s#~>A(&6(#YkU0`;(IWx8?M7nPm)puYGQAZo_;hwppRhGjxUhKQH}Ld@y@ES^$Sa zp=uXTjZ`?r#8fIODtO3nXhNX)u!sM7oEZO?{<537`7^)2stWVq?>-)GZf@Z9yPeek zK09rjE>mbo#{9q{f&kuh&h!2iBkMs{7xsVtvdie4?2Zo$GsVA19Wg@;G?4;ezWwj# zYbf*5-s0aL;zlw5KfTIGlQP1`uuMV(Isf;y2M;#d9t1piFthDb``_+a{8hIv+A(R0 z{^#=%v+4iO4}ZU8q>HqdI)$g*o#c&vdrl$y=@Z|gH}|}K+iQNB|L5V45k9T^oSkHn zEmuV?@(~_=rdjv*V)w^lCz-ggz~=kg{rfS>3%;gJCFkF7WrmU$Z`RU-_WwIAm^Rb? z|Gz2ya=UHm;1DHxTA(uQ)^e?nS=qeo2+<^EfmQsgY_TgE&*S7pMIqOn#Gd_Gw?)_{ zs$w9ELF}@-auI$#+=eTr=(ga&09(OJ&#;3ZC)fVJwfBN5f%1Ezo-G9lD^6`4F0dRXPcJHKd zvRivLW4&3wU6PHb%>V6!&D6*9#V_}tTI_DGPXBEyoLp$b2b=vpf$1PW`jsbUNV23K zY%&`5o0H9<(9NBS!Q2=XQN%=vzCz7{C(A8n95nOr>)lVEoQK4p5BD0JaS8s3l(aB5 z{#MekCwO%bAf^~0eDtCiT;acPy>LFlRx>AIc)JDqcMh=ujgigQ^ZO$T21oBa*SOQ= ztS3v5Li>#|>TPqHa*Ux4WUJLaE# zMs9cIY`WEN-eMBC&*$7B2H=;65npb8x9msBP-J4%%FN#doF0UKDAuV84h$4MMhp&J zFW$rJ4?9SM0yjYLRnW%e;1^MS*y;GmFI;%!*{q$I;Qh_9<>}TqkC2eUdiobpa`?rv z^CmK3q-xSYAuq)d>vjZi`|j;qHda``?xnT z4T;|Nw%lObE;J$x&g&{g##7ed*iDSxrkSj?fN=Ufh2d@4<{UcFSMfzkN&Y@h6G~ zVkZw$Z9<0&p31?QBcf-JU7swRSxQUl!<&u#**{mPCWoLeQ|$587yhCQn}E*2?VXhOof+v6dpO z;;g?i{omBsmB?dy?8Vmgrx~JhIUph=bd7AocbQCan7r8 zpm%SF0ry9M(GbHM6G3z+_r{0!RlZ^_OeOmT-7lwe76*pvQnyVBlX(#IQd1JGqx3-J2`bX+Yql-wqzb z-h8n#eBZJfuNxR>(VxUy3A=XvyW*$YlcN|>llu#C+xhyqvw1f|&x_@N%_U!4dg`oF z*sk@z0_f054!Uict+C7di@-`tykh-^cnZ%As()uElzSip@a3}Cxt##C6pY!hKStzc z*&aK1dwucAn69I-$FXQB)a=ce{(Z+=Ig0SJMO_J#n{r7oxwi z?>y(wIc#`;Y8b)ge0{KM_vK&pg&6dv7uj=&$QIhG^_@V0B~{=@F&NOZ7+zw%4v?(B zIqKc~i~Xi8&C)E=&$ZwFO7_eT_Y<8KLWx;zpEXR=g=`VDeHlzSqm6 z=1z@Sdza~ba-4t@KY;hQe?_y%@Yd>I2tiXEo?*Zcez#0{_YfqA8J1@y0j%Lm<2jh~ z{r^442LtwH=`EIm;vMz)g7@GeS*DT0?xd`|oi&0}}%XHWo6?Im_UVX@4eClAt zIMw^8+j%93VZ_?-TBm*^E5yRVp=@%$Zom0@gM8op`E4uU<`KEenA%AwRM_{ggxAcw zlIp`RSDjDCL6{NAh+U~AArP4{%7Q`Rf?JIB7|3m`gaC3W?+yY^+5zyWzw*&Z=JYDn zWdeM%jQb_A+Gbh;0NYg9#0)i4`+|=8YN}K8uPR78422H8l_3-VyCdp*AGSMB>bos7 zV?^`cHPznH$Mm>=V1PR5e@5L^Jy~B{D~l1MuKcN4f^T{HH!iw0pXay#2x7KP=ex`G zQH=VQ<6FS^Hr82r9|{_w#Mkcu=gZol4TTlN2A?X9D#+TN&PEW8Fb zq9~vOf*_!TNGl3TODk>AAWAnVVuC0LNOube2uOn%gmft_Azjk_&Fxk1y}$Q+$N2vE z7~VVdaL(RquXvvM%sJOO1rtc-L%+(k;x^TLd(jUblI(4%Vhb-lP6E6f@gCdBxQZ4 zTo~3BdXhkd>Cm?18H|0zJGOs+n#8|k+8WT}e(}{e+a)}}Wil6kdqyO}E+*oAdR64k z6ciL;GyiF|zuI&Tdc(}jOulNCzWE9HPqY@AhY4|T+wDXDzJYTzt^KlJn(a@NJ3{WN zIVP}e{lDKJxn@k@Z?qTMrZM%O+nn)uysQjZM?$+Yt-0z=4020`afF9`4{gM%)51VZ z9bW3MmzVi)b}h;79sj;6$(z}c%*w9uwc~Z=^L-&QAxk|B2~ML{PP0}6-pTgblZzcl z*;B~8fzqeHt+#DV3wB+%?LSu_dHvwbHwuyyU7tCsB12Z^LtxSckXKk;x*+&tr)|e# z`-Q$BE9(CPsop~mYq{Fg1+<@kKy4d4m*%ov&|3e@%b+N-1E*egOv;BW>`8o4QFQBc zeQ!irpv5_3OvZ7;2D*Hzn^X+T@ljZ+H{64dN z=BqAEbFR%~e?b$l7S;k)oU7IFm$Q9=X4}+FOX-PqRa3N)M)I?y=G;4qer-0p{SMs+ zIv%dD?yPzDZfzc5Rdkip%H-5|@pcc#l>r$ie~pzNzIA;*b!E1W zL2zQz&loBuW4mjlHOp&dxN5~{%1T#9M|5@Nk&OM_l_{Ck7lJDj9(8OhU2I5y#Fch) z)OiI@(Pb|TCJg*2XNyTl%6=TY*xI*?qmJHJP585$*Z%-&;g4)2oiV{fNjXnP3sxN` zbU#ts9UsZ;aFm}H!15BDFO|WiYc9}8Rbl2m)Dxp$daRs#w|(v-cdbQ}`UNW_hmvdA z-A#@oP5pqhPggoHiBNQG7ns^H@+HNEdh+F`YLtfk@3yef^&L#q$fIQVPgEcwA*Nxc z?p>?gZmPkmmC>=4r@`}|b)|b}TY-k(nDt2?Ig$+=G+^b}XT|t=d1UT8cYz$ z|I4LcbDI%h7Fjy8t3Vguwiw|gG+i%0;4o$vGCy|Trhp2uXv!xu*Yzi6ZBW*fLTX(V z`3dBS+GTO9AVdSGzK#H1HGu55J^wqa$2rQ0I`PD@&-@FsF57q?64@73UYoi6RN4a? zN9z|~CCL1WFot}{4z-iPv;4Nj2inj0YSXvV#Z_ z)g>4%(N)Jbi>8{ct|K;M`lxg(`6nfh99d|e&A&jQX@Bx0$4GPf6UWtAr;y;8H!(G( z3?T=5mknCYssw)K&w-c_X{TvrClSn{hd!GtzIAlu+Hm||Rz!-2!|@S7#`A9!<{Sp) zL&Qz2iw^)lElpO%0OC!YKR=aZm1n3hyo?kfQORSAd?T|slBqduWdxv9DY$5a8Pek^ z(XTIKNBJ`}OvMNVwJ(oaAti6o7+=fV^^o?K@P9|^3?qu?Gqm6ZrYM{yD9p!;HrBPa z9!%6M2qrM~ks}1QTCNjX-ksM)o*eoKV28++1bP)%{8|;g-ErK-yppHyrBjt^`;Wc0 zi!B|}3_<3*M$)PTr(9^3oRyL_q=*-m=R;O`@`lvM-j)8h1%*}oH-{P%-HJ&HNBc5b zZWfEFEh6tznU&xJ}JI4u7C zp1=S8e?)v>2ja2+_va9iy8id)=VbVw7yrM1dRRkeJ5SG!ga1jBKd)xJhr_O%C9{+9 zf36MK5P{QM|EG0JtHW(Jmm9QKRwtTx9ypNi+(EwM)|w6Emv*M7)_+zqlNd>esB7Xh zPSGxV<>&9n!KK2h&!aE({p|~BF4m|x{VV?W4?I48^P%(RyKi6rwYD#?gT}3o`R-<& z%`^6DzD*zIjb{5=XE!ox^f`9C%|?OD#LPUDy0~%czC&qWUnS=+h@+nON={BjnYz*2 z+dIyrBj5ZG$FKsPffF$;jwcYg?Nk z|933ROHcN)?4hI-*InCmR$o6ZH7(5|z1O&|uC8}%jE#YT0oUAdc>Awjsb}NRt*rm- z$B{hHsvB%0T7?A|RGE^Q>03~+sy!PZc{E+W?tAK@Ob~C>UKW+5@L8jD73~xI_Sv7Q zo#_5#(NnV1`^Ae`L@_REFHg_#z`%o75|v!<-Fw*iXQG4#>$m4-UAulgdA@aM1ou)D z7tl2N_RiHa5_P{Gf+OWAqnC68CC_P_bD;Tb-rUpK85$aT=)?Q>B{$f`;sfc8I4OI- zeG7Z>V$byOSNSz-)@&6#bc~Mfd|y?FvZZCl@x4!)GfhkuweB?-D4Lq4nwpv6VVxu% z+(hmuXk!D4-;e|q$QAe~?Kz+W0T^nL5)A8f= zRYI$;Qd8N9`+45rq_kL=()X*-nI3IZ4Gs?8%75$m`1mb6YHv?Z$+Z+lc6K?>7cXDN zE-l$}88<6@`}Xac`#(k9#rVLpmt|#Ty9N1jm_M;Tefl(`o7FxjFz~phrl!b+3lg6E z{QP^Us1hpzxS4r*RajYB+4pTFS|KpoO1p96M$$ExD!wJt;bwYc?RlvxiDgw)7@>l9 z@7_7?b~rQshbp_dsrbS6Wn>Ul_N}HOLuU@@c;K!A+>7NyAP}19Fh`)b)Ypa@#ot=ay22PuklAM})CE(?>#6OP}8X)hVal88? z6DzAvOiYY%4qAHq_Q^E+H4Q9N6{V%JUPq;jdh2%Y-tFz>MJr~|l+?!3v0aq z;KySYeSbSm(zoIVhTlK?O?L&OLHHvbcsfizM!NJ`N-Xkp+2_v+a&n%Vw`{Q(tUm_M z=L+~OMG=t=>o(QDbFFSd&pdzr9D{>DgVkX!cr@HGqP||?Bo2xB^%HuB=T@h4S9dpk zsnSfk;KPRxFJV$*Ax;;r+s=66#3cc{8R9N8!(VCb?Cj^mb9lXyx>ieiRIONP{@O`Fi4K|g-{;Jnp&hWF1@YgLez zmR2Tnpl=x5Cp);mFp=))QBf!Xul6n-rK4lSKTvDG_;4*O;&;E04DO^=C+WOK#cO^E zt5!OQSG&a$>xyU{7Ld^DQkqht3cVPw(@L)3>XMmMh@d)Z;w0ldH>cIb4q)IMy13Zb zi}KNuHDTu-(20HWplL!p8PNN42L!h{@6)`z@rX#xd3Kq`Eu(EY4JaWEEXJCWHQ5px z;z71agXVebxHNHrxHH0tpP!#+B<+Q#u_90QW^lQC9OMZ{LuzXNT=vM^hEUDSRx@GY zyR6$BuV1?sPvwhf_xA1S{aZ=1-o9l#azyypv16Ob$P#xgef;p@>)j4Fx2>-C@7Mm5 zjER7deo0O3)vH&>g9Pj{K3dpxcXb)?vvG1Nkdu>_m6dtc=qcFPWC;rk8|1EZeR!hH zspNP+GCW)u9rn!E*O!Tj3DYJVSDqeeQ5ISncR}DwO-;qP6jHVT1TwpjsxgR0$Ra{* z{_C%22?@+pzw;dN1o!tZ9Hpl}#>jXP%!W_tjri#-QkS!cOU-Gz9$1^<;o&zFK#= z=gzZpzb8j{J%=+XBQvwa*|TR2fN+9@HPsbHdFbgy;^N{SKYf}s@e<+l!>3Q>2I*x+ zrwl=n@q(<6EW1)Hv^?dtcI{d^u{(F}pq%2+4!(2X{SEdw{kmvm2010Az5jluY7%s% zlAxeqlYM?>W+uOYK=1eOM@>w|C_M)1Vm!*q%efpD%!!GllXgWa;FLEVa?_W0x^e7t z6`w!T&26}Of`*0$*YJ7zYp|VF1BHcTxHUvYKPKBQqqF3A_ZM<4^S@R}%$)t zAR3R3j{37}KStear=B#@)fH|t`F#L|QEbLo1$Re1ic$P$7W{h-ps`2h@DsmMy{u}n;o(Pu`Twjw5(8L`FFZXM zhehAGZ=un=)&0cH*3*QF!HYoa!=}lK$8g`zA&R-|8HubdCI>`x@ZiB0&z^-ImGak{ z>aQ7WNbqe$1c0mu+Ch?`(#{vW>h44LJ} zWIRQ;hmb}Ld*y7anMZD39+%aq+Lhxzh6YWD$&=qJa)wtrJGDeaMF;z9BK)zq=?1F9 z&iV7(q%O>kCEvTZ9?4Ryc7C#N8q$>n^8aIZckhsp5S^;v#E1(XOb7u%ycTk2&NySn zHl%7xpr$=1v@#b#7l%oU!FiIKn`@3x87%m(Ub$41?Be1Q>@agB-MB@mDRuAbgoFg0 z3V-bY8&wLW1jPs2sNz#u7#T}4GH#)f^kM@80~yQv_wVO*{%l+z}$A8qA9 z4C|kp==Qv^#SE~dXJmv$Y_K&ejTg_^ONltJ9u?c~pZdK&MWB0<^r-bV_KfC|MK^hAqf&z4V=TS;4* zax6z`p&!WiQX=Q^+r%~@bp6(XaM$+*X}6wVrWRym0`L3`V3;a@ujgLRDDWU zuZ9WE_v|L!|GQBSxg4FsB#P_1KY19gt}HhfI29Pr4EH`=<~k@~t01Rc+P6jc+_{9| zgoND;(ic6U0Kf3@iGK6u4O4IUorTI9?D_~(Oe`!>9KWwf0t1Z!>*v>bS|1*h6S*g{ zEVWXGf8xEi$1+!BObqj}V`4jZ?xf_t8OYAgUY}{ADORhj>0lw%#BV(w%Fe;j5G{4U zecwsp4I4JtqU^KidcP?TVjdoIb~JZV8qseEv#Rm^qum4lP<1#txIKW+DhU)p&ga+O_elpikjq*knvat3jlcfsDH9u8Ceov!m%FPGGsX^lRxLwJ8raC5Iqt$KF}D zk?G{gl;-ruPo5x=MP-@w4c5vb?^-pv?dP!m)1S^SCsbtu`K+W1Aov=#a%-3EQKww* z0A|iteUXEnKB_rA*l}TiDVc&rB@&r`g-_K_GBgB^jF=GuY#9P+D_?#KT>gY1SCtVgTC4`Jp77Y!l$IN zQW`k3_Qf&zDF|E2S;1It#QG*@R?=&IdpiSigGm!wK@fP8B%SwLUSD;AVtebub=%p| z#P&S9x+}js18C9NfX}dvxFmy2BM>pF{v=9|#i=@(zau};-;pCnIE|Y*iDf#}l&q|! z73$Azk_1*o-1Eoeq_>}6+*ub={ZCJ+GKPj*v*gj?$tmttf{T*dwryKz=~#(Ij!idg zk~Ybk_5r+jV?S@&avQU8p)O>Fj*X2iD<@|t>;ZKx!m5EU1L%RvTwbM3=sG0zXz zYCuILU@o%vUvk*uwCRr}BHkk^7@@Sgd=$eIg0cTpLNZ;_Q|dJhoncUDb>72vdQeeY zQ&#r5oVt3~`=~l%9F*PmalWyG?x}QUbQBY9h0QXIW z5J88Y(a}ohbJO%oc~gI=#rKp2B#w`Wtc?}C(BmZ&Z#0JT22qTB-@e#q&vvv3Mbt0? zDv5alW+7BYKuBapt#b0@NzX_$6XdZx{1$JA7xD=xNR;Y9LQd@&EJ8w>$T*MP+;YFq z_Vo5f2|6t3L6=ua(;?Fi4!i&6`=0_G5cQHW^0F<2jA>{T%3lSIzMMdN^ndh}5&<1y zb_xV~fBxdAytw$|5kQdBEE>6&!@|M{WD!}`IZN;#Iy%u^U0tj#GvnWiiGm10SrHLV zTuQ0|qfYP$Yu1s1$>6$CvV%o4U+t)u7_js8(BC)=;B0HB&kQ7My;4tl0$~`drs-6? z(JES-hcNMztcWvi$?)gUeTpS&etVqHX~iBOrZs~F#XB+~QMw^vv$ovCnkDiS5wO;6 z-1egMhMc6N8-Z}ZxbEU*nECAmSPh@8PgLdR;pH_bbY8aL@jT=kxO&t)>$aQPNYe8OcIxbm*R@6Y8Gym(Rn8mV<7HY!(EeoIj znswt_v~_gKfFWmQX4w5C|E_4Sfu>iB7#=pFI(_R5l6gG9nrQ!TP)~9`PM1VatKD{p zFt&(cIyV6`T?9$|>j%ZxnROh7jmLR^cO^+HyPNr+e~^&;ixt9?|3em#kU0PK57R($ z=HGCGAi;j$frRAz_?AuzC$j_Y>;V<#>&Fl1o(jmB%Y=;0qzQwkay)KqJh zK4Q6+*426a2KU6Z^qf&%ymc-hp#BI?|)I}>fLzg67v`t=FkKkYs9;%}Yr@Av%w z^7Q|7#WOlI@t=PVmVt+2W0RSmp9f-)1NBE&T3RZG8o{9TZ3>7A(02XDe|9D#fI)}- zq&rZ1@_;=!s5Yn7WpEx59Dk3M^UxtI+r`C;M#;+pSmzI}5k=`8@ zyoRLEr1eK{c{0zGcO)1btfj!Gzo`Bsm#Hc9k*{enAFF5O=;`T+S{%V-;4~6nA(>9P zO1jOY4w02(WI}up+lX~E+h$c>RP-%7Jq==KukqqfwSd~3nx~g^smPHjs;sY z;HMj0gIGV<%IX|2H2gKqGZN8Gy>6Z@e=Zz(OXR}eL$E|jC1`&cq)GPF*hgYvYH4+n z<@qAzg1k9LpxeZ%+m@C};Kv&x#dbINR&xsos9RJ}ZKL9)b14dSb|#?~Z+pk#7{FzC z^umP;qU&NApo2ur%~=afRi9hvrdf@JlqJa^6I>rI@m4%iBz-;?G0nH4KvN{Nfh z!5T1l&ZaN>e!eH*4y_oGt&P0+LsbnwCWAwZmh=nvVNs3G%~dn&t0b6?edL`-3CM_7 ziIBi$(yr#&)7>3G5MQ&SZCE$W*%k?=rlv%;g$z14nlsi2P)U?2U^75k3?jgKq2{TH zxR8$I3WAq_KHCN7LP1Tuv7Wewm^?rr^p_{EW1%G>6UM!MeFfK@J_UFrrctT0JZ(@ zq80^YTVE(wuAU+9CjQZj2+qWb6Fkz(?6&0CU zS^1*yM~%Xo`LT#OIrmylOnJIuizO^Iuapu{o!k7Da6;FBY_0(@W*jy~Wz~8ZW4>X_ zUd}Jovv;A9VBWR9$QYt*Tv%9W7Vg;MG}a_S9}#w6eK1QoS%au%!S>RD#Lg>Q0VmK8 z)ipJ*vZA7)%$qs6yz|rh_j+CLA9+UNl7#y#!vQS3?dtMu!a+?XrD()A2k2oyK3;T0 z#}Bq-Ca)}yIT^xR#V-&$i`oaWO%!lf<6CKCH8r(#V|+e6K6wmF;7R$pnCR$cop}|^ zU+X{+-r$rFuAv;17^F8^hagSa;_3Sd6Sy`EVy0byMacev7xb5KSLvg00 z;|+q6rcsvaYHB?_J?0 z7Gv#gdl#Z3BRTugU6GM=g!Ex)X~}ILcTt@^Y!WnTwpxD+1Q5gkDjw6wX31W#Hq=U7 zot@k$5hFz1v%5~BurO%JNF1>Ma&LHh`#NyVGDT(wkPB8OAzPtEig&^F(Fhzqd{$07 zwjskc1_6WNFO?Utor*P0ynA=8!^8&)TNI7GLqiGZZh~HHXf`)9^NNUw(13r* zU=e{wQBKbEtLAYEaMDajb{J1D{8)~)C!&1Pn8?$JYXc9UX5nzh|E^hR{)vDJ5HIrJ zW|=`36Wv^{!1LsdiL<{~dpJ2*~pY3W9A zf93<{tG0xhKrD6djbmHM5vm^-7ifw$H5Jfhz?NNHdDLN66Fj* z8WE)}QEzzCVYKo-4%yn-8DdKMa~b+Tv4~LCu!$1CMb>{bx#7T`J&}NXjl5I%3JnGZ z;Ashz?+y75i{>j!HbMM0au5ND)Bz4GK4>>JbrOohmc3CO8ODTT5?_S9i|6#|SS-}k z%g38jwVz<6p#!wp{V-h=l$4I5>f*ZDdf9Gf__@M<6*l#31#NBZ&0DwDKc~H*aQQOh zlMkj}Pt3MvnL!rtM9+$gi^D=uN!dghYn*fDoG!axJ%R}W<1uFDC)@t+&oeGSqePL7 z@G0iY+BXX<|3$~68S(?tiwh~49HJ-Ql8y`mWog!DlLOnR*MX6)QaOrvw+s}r;CZu8 z^F48Md*hlBnt;lvP#lC!D!KK-|j&<>p{h* z1fj1Ua79r?CEjisE7;|PPKZ!(w}HTI0G2l7&6Eid1T4bYeTL>xfU>f)kxRvq zT9PK_kVk?}-xkKC^8xJ*Du7e3`jCyabu3W~oVR#$q79*KoC2)J2@pKQHJS* zewXg^0ZPe4|3LJMvnz->EMeGdp{AS{nT!HFbdvYMLtXL4~iNO?%!|DCjx7-gpg!d z`T81%g{hRh{5O$Pu47-pK!=xQfc*ZF@A%D?Vz535SmCHl@Mo9xZJRlq3{|=h{OSuv zUW>uVH*YvWvKbE4MzK@66I2!<03(ni+78gH&OHdW{~?9}E;C3sK%pmo@#5QFodt2Z z^XK1_X?7^btIbRZk28y5D8Mermiw?6?|$tjdfnY{L1WO=i#6>2&ZSY%v;>>g5H7OC z{UA?jBgf7F<>nA2^}qJUlWojZGPP#7G5H3^u1H1Y37)stDp!{`&Q+Kd(hR!pqI9AC;axNTvW4 z0gd^vN)Q>Nwzih3g^hK|)2w@WNonCCPqoCma*8SI1$M*c`^QJ27 z-(Y}xL4Yw$`w7>(E-t-*)>v6XXn#G{E@55xMB~(YYD>D{ZBx_uy@8GQA?w|^5$Pt{ zEtO-152HfF63uDOZ(g783Bhg8-o2Lc9S{C;Oil)A+JtI~skkNkUOuAT=1rSwpks0A zRuZ-f^;|&{j5hhfgZ5H~6A?X-?nIE!q!W~rm9AXz16D%#w3zCb4L<(6&CZ%PAA0RIJQ8 z3c!-D9~Cb$Znw;diUN040=EHf*ubG3I1yC_KUzq|Z~a=@_~XZq;_+%vX1l7DJ>fh4 zS)TwHsH9vI3F^Sc+(#=bvjv9Zokf&*J3tv~5ujR{h6Q=JdP6NKxeOxVO6mVVp_%*L z(AvhP<`-#^ZK1OgfB);7-A5&3n`YZ0#k@#0zrRKG;i(Wa-lTOR7MsZoYQi^cA4@Lj z?CaCb+et=XQgHU?-50^*Mq_%E)YPPWbbv3LX8*BPQ!q-74MQev*@_?}%{iTUR|$;| z{t8S(FywGSe3N2(;uRS9sv$v{nT>7Czj7997ClP7ckjn_znBfRji|X{KnbY~5pw#_ z9T*e@2ScS~$12pvXk3@7I+uA>L*|}!g?$uHn-P{Ze7jQjgejS7~x6PMi_UzfykAh16@?|17S{C2!`0#{+ z5LO7vn(i2iujITL%5{I56dIs5xc^LXnn43D&NBenP6?7chy|02Ilh&{#trceQUS(W ze@}jbVIo6(Dksu10eAt{>mHLc#}CU$NDw!*9R3mp^#D?T4G|FFwk-fA@5hEACHxjl zb$no^(<;8=$5kH!T0`?e0OOU(kX1cg6nu%ifkFIUHjP&xm0O+;-~xM(Uk(*oov$LQ zjcm)2co47sK-L6YMQ9}UNy(S{vNc7Wogq}(l9G!p4n96U?A5;;Oz=gwBnTFM*Y4fZ zWz5MXS|?B`QrDu~#B`z;Gs&Ni*0BDzq2lGqb68sq031n3#|bc!5HJnz zhYYKoGt-=I*xT0^gTSI}I7=iwQ4i`w^z|Stq23Q3JV2~doZjdg6hu^rcy0|sjzE{h zV@fiCGbRKNG&{nNLw69O2RzJe^r+tu4y56b%7t2*fM3DQCgB8G!TkH@{RBt4_4Lhb z$as2Cc$N91t;X6>hH?k}CaZREIIgKLgVUJ?=7NNul_GeZ%NIPT8;D1sYKf7d0RmP> zd6GSn(Xwa9j!*)oXr0glsg4a4>42&hU|mtx?00W)T8}7CAV?G^1R8~}VPZuk-GJD> zBLgf$39lDj9Ka!#5m5w?ZaWwYOAG`H3C|Uf>r8N}5RZabZh;zX2+VU6^cZu@Ui(jhU*)3-u# z%k|68asObpq1oaV0XS_YZ)Sbw%2I-L0 zo}Ya%Xf`MDhk)Gy&aJ*dMrfH}+bRNiSlI|seT;B6JURvrd56#4?+3U?Bu!~dRE^n5 zE81E;+uhSc$H&L#PRS+ivT=J-Mge$>t-J~}#T^m_KX*W2<`Wk;hzJcm1JizxhU2`p z$ocaMEggAwR$vr!rHg*n`Y1?Xj+224pe7G!xxtL}^S*p)#5009T$;O0*g7)ckzrjjZnjQi(FNw$U5L~zjJ%bPvKz_#(HW@(N>z%*j95F>&*hyctKNxyQ&?^d1KGGc%w(>Zg4PY!2}c74k|>W+5jFKX5Uw7S2O0S*3x)(E zONro2=ytch2}7ShG5$MBgr4P?o1ecjK-&4Syrg7KF}c8ENa@3e4-Emcd~mxEA^;G9 z4_K}>BsW8|SN=Drg{@t;Zs6jttIfG0eE!F-GUOBR8j6%A3MGG((nM-UlyRy4<8SL! zhU2hPc^dK-7G=LHg~p`<$K^lNImu6!UCVK0UiTj%pi@~V@{f@4_n7~FoS*Oi=O3W( z|M^FOh$W#Py!D5k^ygxZhq<}A2_oA$;3y~O3+Z2Pch>BOmQGlYvK{`f0s$d(Q69l; z!r0gBEHqI=VufpH;ICg7*-^v>=hC4=XE4ZsvRA*eg?46>PIsYnVNt{&= zVqRr3Dl02vz7k$1d;-<9`R33Lr7SDl0WX&{G6spb^d;V7zjI*%KBBo z0!+t_$wCRkYVYN(;{6>7@VS%S%Fv<|HD>#3&VllI8*OHoKycgNKd0o4*2Q>;y6p=C z2Z87)iy3cd9a-MnpEBdB&_`y)XdAyIjOH3zT{ zC)OwQFM_&9<^Kj@I+*=W&yZwC-PKHJQ0bk9#UdSM>~iWc5K-90m+;XnxdE>DhA0UN zFgPlZfk6H?y}jMNXaD{<;9`Q;%(I=YCCG7f4hnLh=|N-_B-tRi^%H@h;t7_)+`Jxq zZ#a5V6LFydTH6aq`b0?uqMy)tQO_59-6!qEtR}J^6c_@>Lqm03UWh|HiytyteES^9z zeOM}({|XpoW#F&Tx82(ipnl4_Dc4+whlimG+{{UaVf>=!%xGHz8i{d{$j{G5bnQKM z&LvH+<_KXEAR-(cK?i}Zs+C~NBLx12Q$O`M6dvv{Z+B+)Ny^3vUQSUxU`Z`o8Fwv< zmxuBA>lEsoWS5&``_61^kf+xsSIL@xMxDj%R{2(J8x>z-p!|oy{a^1^{ym)WnH-KF zvXExWS9g{|VM^^=*@d9*sURyGhUb;=tgWjvUgThBrxSyC>KO?Om6#_Oaicz@oGMFu zd;44zI-Ib*fMeD}?tnlY60f#f?=R#|s7+BQ?OPtOpobb>UEzR+D;~e(jKp{VWLgir zlmxgzF$Xte+ArA!L?=YCI1s%wPD?4&*q~-$On>w!xK(4!&^p4}6D(k7fC)fs?S!6w zSd7Rxn0DCYBXjaJNwsqj3vY5*vZCXcvA8=Y4R%*wjzTvqPhNEo$ycglXpdS+Im|t0 z6)bYpYl3u{`}GZP#g9XCXFndG|5mt(Wm(X>bN1+ht%QH2Z@@2)xAUOlxat8Zf6nKb znX}2wq8BedOpHtn2ndMucj?{Ig7TGImkKL^STPE)*THp|K)^K+kNz+$Aye4YR%^YK zJW8PDqPrUiKn=?kQp?T7U_t{R*e{TzWk`y6&N_h2B(N25`0JxfZLuG=9*i<5Lw#aL z0EdpGyD>r3fOe<@7UeYiE{T8|i;zZWi7azcVq&tOX7=qP(d%s2%#aYF?^Ri>yp)x* z)Uk0fTxIdtn=7>5fyu9p`801fsy=aQ_ho#Sn{5J;KDXV5&yXWZ-4E&p`LBxL1P@u#Y?s&bKYW1E}C#@t`jXjS84O5d{B zi2T}>)k@N|9qZ}Q@QSI(gNvTCnjVm^;n~fVU zvI#X;Sblz7v5`x+_a;?2^NY-ww2xCB^|6lL1tuPS6a#3j_pR}JuNup4&pUavMNbWL z*e(Teu?Hj$PJLlm_^~SRs#?cA{*8W$3YWuxW#ZC6Xyx7@<=0NMc}qfV^&{6zD#VHZc53FlaVZE&R`L{U|KmSv+B?x#UD8RWtnP9$e~4frnK}( zo}g1&MwCu@+lRAO=3DD~E?n>ZKCG29<|i}VD5Ol!#F2SMHgSnl`$pi(QmGEUHc&KQ zgVldpl7?$CY01f5q~quif*!B><#;V887azE5i9e}Zw+XOeivLa$lOZm(rCo@YOI5* za{J#@&n4Q3o2GHZ;x|NSRb0U~fGnjx4FVm;13!a@lY>(5lv;pE4Cy6uZI2t{uH{?bsAGwWtYo|==5?be{5%G0_$c8e@G&wXV26`N&$bxqyEVB*>4rIm=s zTPJG1WHyx;Wt8Z>S{f@78SG-^419qkDX{ppqh~DNyw`@_P6m(25nagB*&Gt0Wk`Fb~CQF7A zi5)FnCh}9G@_EFSO)cLvO{Nv3rCVgbR`AQ(fOSi2(9kMjm>D%R7BW_?xjJiU9Kazi z!@nnrp+h~UXUa=C&v$d0h)jNthLy;MV~(SYQk1nrLcmxE@@;(afiDF4x zy>QOfRe#CNf*5XE9^L8MR(IQ#=d8^WHDjC>YHU)=oEmo9`@}q>9VyvNw(A<(j>#_1 zwf{A5mou0sqS_!3p0#q~xY9S4y!6HHlMTX}VhcYWEUV0Puc>gF7c>sw6W>0i7ZszM zsBGgZ;(3Fea;lWuZu;Hpqy@_b+J%lqzCoGEQ{@H?ik~Lm4_p&CHFAJ#fIK>+&#nG* z%%a}6>>Dg<0cr^;S#m8(2~$(ArVZ-eNd&YX2(voc8fWEWROeyG#Jt2m*>1aG-o{`^ zNYtP=z~zNm{AH&rucr%WHYetenPfy6mKtt1XyZGVe%|IiZ;eviyQ(v!Bhmn<4 zNlD2Eia+y{4|2v%YHES3+(X=~tkR(5h+AUo49>FHVq!?9e*jtG{JZ5&*K=!_^h)qJ`nZ zOV?cIxZ@SYdStjG4Mr!IdxjL}`Fi8Y}>6B4o z|0ze$NJn*-hQsyB369e8$R5hMzUmvL`^{zJsGUM14vt6-?i);KKY6w}H}i+fV`eAb z*hUu0p(5%;Ix&wKV+L+rJ3khs4Rf}7b#@+QF|yhrmSnTauM1*4>tvYN-(L`+*ESd) zrPKXXwklxlASvVLmcCfs>ms0%AR{SZlf@_IN!n2(YT~pv`Bm#$*QHF&(Z`$R@Nv3< znWt-PyETQ56@*@VR$wU$pZ zdk2WPGv%?pq~hz@lg0sqdAbWFna>kus4L|SMDqogyIoQ?vLsZc-MN&*-$9ONP7YFF~ z%50pjFMP?|uDe@C*YV-s7_ebTt_qt4eW4K(xCIfnaWD`9f%*_l0ZiyjVhHNBfLw&J z7q&(uPiUK5=KYtznH&Q_gxIcD&0~qJvrxGP!C?}3<|dyL zLONj$00(RVE{w2AFMtZQE(;AH|JUZ5j6_2RF5Rl&2H>H3*u+~lG7y3lYK3K|nNhy{ z=tZZ_xuN!eyl00u?K*jm?%@v4%O@iq%BJ^aiW-v3Dn{rf%B967vxy`&s~QQ*a){Dh zQ}}Dwl^dnGSp`Br%9pb}pG_s0=Z1JZ_;ju^tIxi2KG1AbYIJeZ(FsWQ1P2E#HY_bJ zU?&n_?whl%T{phRL`BKQD;yhHoxF=Q)6fO1d0ADp;1s@HsxW=W5v%;sFJCT?6dv8Q z@_1G(Od(W`sid@q>V4m-&I|Q}tJ@@brybZhDaqJs)A~8CYvwQWKbC4Fb(}2g6TW$f zTYw`|VX8Rhk=kER-et`?nP;$EU+!q~%}lSx0g)kPcM~bHsQjE&5)ac#Na>Ra-k3%27jiVWT4y=G)$)equKdxbye)h$PBE|_9dQ5jgY&SroX-81`vz8L)rQ6O z))j2O?s{AYAxq`qjmD z1HI5%(#ADgrxYV~zt0r8^wZr<5>37Fi9E<5?_fR4@M+f_!hapUNBnOtZKET?l{h%1 zCoia8dU+MY-n41^b~dF0Y#O%@j-T`H0lK4-lqw@L*z z6K@kuo1*vcKJGt8zS-aIh3?mWC0A;XJHg@%#n$7kE}U^Ag-cf}POw*Z&v|cjQ*})3 zuWFI-OR@Q+jegwJ8OGu8Xtav*%$+XxPo92>-y7DRX4_af`MrgunfNZv|pytwh+-iAsIZA=?1oz3#+Q*I?lGug?k5A<*Fukm9 z8A`uWw%I1(&+x5xN+~pHr{IRwun+RT>4tlAF0h~&NIa!@;OeI#=O4 zQBm|rnTL_cNX$oD{ZML$DJ2}cl1hgSTE}i_g3gAwV-nk^*kR+-psFU8Craw`wzj(bi&c0yGxrIO_cuj~Oyhl2rb!fT=={PohTHI>>c?=H$d%5J$pUmGgw$4xCU zAa|5srlM2C*5}mFY-ViV>T+v(N@v*!AMfJIM4^R$)3K`F7MY3L%rOF$FWyKV3HLHz zC@FjQaxb;?oAF!Wes(q=S%j`c7LIwh)NGwdPOPvb%c7ZW96fNMW!Er|8H1)%ueR|4 z%2?}Q|1kC0BLd9a5m|p76c3o>rw_Wp}N@I#b{!9*80ZX%2bAlV_&ZLhbvqxQU0~|?(JAgUt3@^Iq8FGfBDJ6ZQ>KjK~D~lS{})1=6Q+FO|Kep0?Q$RPx?C=B3c9{aj3!yOnD0 z?;omUzpi!th4==J#voeW!`@t)j+cff`)gWVtM<}FNiA=-&heq0tCSb?vwG;X@y*BL z`HF@sZd<3U_XM23CQiCH`u@dNm;X$d+^WJcI3}v~I6;YpU2Y*yS4i8@HP#I&wKd-% z0G8P^yyyNpymAy*_%i1$#5|*R{cDmI^{1l|$uZ&a=Z-BbX4OXNa~WUh*-2yj%s*IU zR^P6qREpn!4>@?q0sKZ?gEM(&w+{!DCixMep7}drHR45$TX9>|t2u zeE)NY{c|3C6LxFyquw=%102_B5m5mN867uOn23mMN$4n6Jd38y|z)#ho^7_2STNyZ;IvhPdMUcln{@N9Ej=Jw{&tO0PKgY6Xd&TKOIC#iSZ`EspYaD)O`5XL>PO`&Y*f@slP- z-S^_+*vR$$L0i%K<9wBmQyf?89ImUsdr2RmGCuNgQc~57)Q*|szNLALYTDqLo1v@q z(-)TnJH*8}&MnMm$u%}_O;=eb!pN4Vcit@e)yjqr*?`xnC%V#DE%eoxt%5sTY5gas zn>je=jc9F|=r!<-3vHJ=IxMKA4ERn&s|w^VOi*;~s$;ya{qen^hREiHwdUh-!iA~b z_TwK;NwW^hYHhmxcd{ljwrgTy;&*i}9JQ%DoSNu4!Mu{R(qEhK(>|NOoN(HD{E%{z zS{OlF@!WUaF*dV&CZZ;^M(gdNcz?r&pV;Bvj_`evqA&k_sh<<|&>hWx+WY%& z7d!t6d%v%;e%BoN@4x@LqVPi}I)Q<;zd!Z+x94wT=e*hEcd45l`BDlBdw>zng_?P9>cQPNOqXa$>rNf885IAdh+f4jd{_U}$&XzyU&W1V)bH_y_vjv)+QMRY0k> znHj#4Z-KqM=S4(r_m+{v7T^CRh1Q!%(Np@IxWJaf?I~A_@5?5taBZXJe`x}D&p~V; z?|$dHrB+t>p%pgO3Ka15Z`pC+Ksh$uMbSR#d^dSwVEsQ2F`eB&VUN zf+xBJYc{I;nVaU}_jaLjaW6S^x4|9$p`@gr&1wlJR34(Ey9dQ(Iih)Q*BIoBP0rB0 zSCU2;BwwBZ2X5YT!05@gYr=El_fKaqgbFJU?D*$??M~W!5)u-qJS1S%T;0=m3%t!! z8X=8W#(>N)*u^R^VOMdl=oxlL%#mlw$!t)AU#X-Ive_(go3t-o$Y8m*qo67HC5Pl+ znvY1y7B=rc!1#NJ{wfNIOjQ>aUW@%*J$Q<-<+r zXTE3s0B0!g=QU@9EKJAC>(9M`7d~N+oE$!EyxK~|`vM~Tn%CHyVbBmSg0g&O8g{`j zsOhaaRx&w8FhO9O5=FX-yGo+YMArvwVek6>T@=;)R~Xc!10eT=L+!^dKo8V2)!4Vo zs-C_63^tv8Nx8m}I3^mGy5~x+mcfae!`|LKFz_fK-~e{BH67bvH`Xoy<^3bJsP5x2 zrGu_f^y$+BJP}lT`A)$thKtbMr$*ZXU~1?d9hHXY2-)SnWe%_~E>sE=r#`$hFsQ%L ztGNq@Wl06&J!^)u9aY6X5oe3fJjie}A6)X~dw`K+H|;60U!^c1&Y!>`C>?`>Fz&sB z*b`;;XG(5PGE!DENcV@6Ayg@$qa_8$qNRfHxMtG#?ORdkSr;7bNlxj0AFVHFnmj|y z%oY851DrT8brn7JIf%~})&TOkY%u24*dGisu^84X*@Omr7!9`WJK>Cree3PTzHw6Q zVZ*s7Tb!Rzuw64Tsp>8FP4~*u%(Z@wIxGUyQP6=G+ZXYzdHKZt7VM&Z3U5hha*Kv^B|qQ)KR<#9j7t>g|9t`M7F^8=eX}{CoJNUcY`_V%3Tr4ok2Z3rESN zm=DOkN=*EkW5pKYw88`9GDb8!IeB{z=?-Ee{L=g+kJacw2z=4dr64-@wwRnXzHt8h z33xBCrRXaTuR;f4b8IynHn6EXCnmDMe-B8SYT7GK9A+f$&GZ5F#eN}2ez-tS^YN8S zm3BBuyCEXx&2WKZC7furSMCZO=?|D(J=?*?G&6Uu1*;_jYy8FWE1t-&;g=ZO%N(%* zSxQIeyQ)AV_7*`JIoFgVH>2_=7msNo4nPs;g0;q)Z3HI*;6Ng|SH?KJL<-?pmT^zB zmI>a1vw5&_utY@U=UQ_XjW{bK;{gX>8jL_8ElP{u5dx*v}tsKmWsg ztUUpBJUTRKy~78ZZ(75yR+Lk&t)_w)Gi)}vHQJh$nV|_nnn^inNy%dxA2-5*$U?FQ1k7rJ2#NzS>k*S6o{92=187Be_r%PzYaz28eGjDhjW}if4<-T=h>2 zg0IvfpY~4cTjw)t3y|1NmgXkzJ$R6Y&|yD>h4j{bo*nI~pP$9T0+q3*t#AzfL2N={ z#mjW3PWi;fG8zx)Vu!>CMJJ)OR<01IejVbz{sqeeVZC!~tP18mVnon#=0>%<5Tjq> zWL<^9d@_~GmwOg>wO3a^yUQN_q)rL*;|B{{EDKJV3JP&|cSqRu3V7ypb6ojue)wBX zy?whg+^$b3Z+56inPYa_ta?|)Yf{=JANdd)!0*8WU5(u}*!v-j!x)MRBALA3zkffz zG;c<1Ne6bBpDkFeK;R}~Dj-21&ZL1CFF&IKn+@-8qbh@|_%nLw3djMR20-jIBO%hr z9!D~a=zffqjbPNw=LbKjjk}z`aDfA1Vy%ZN;v~+bkuPv` zK$ABXVtQxU&9XvT|AuWBIFjz9^|%(&9})r_8eQ0B4&%j_RP8MwUYnFs9zJ?>6=wLY z``Pcow^j}Ngy8a&JUCa<9_c%`hQFNrf7p8usHnECOSH@Zb3#QyuP6o(6#US zMUcB)tj4i|JV2S9o>Du*KhO~?fCw2 z{gFC#&e?mfFxOmj0hCQc^cQbGv=jB=NhXuhAaOJaYWI=|E6xE}CDzYrF3-f@oiu4v zaJ~q`2vnvS92|wMe@3&$nrKz%Nqk_rp-p_ABRE?^xUB%~bsF-|e-(vs$%nY&Axv=$ zY$N0j9B|>dlhcm4Xzs@2ul68tRDFMGH4a3Q@6a#!cy@Cruu&)!gpriEFi7;Q|Ak;Y~J=V^2g#yN{ty*dG=Fk6t z&}ON)1wS$Uh=PnI_#n}VU5w;$r>fkYJ9oVO{HWhqLZYIATi!nM_P!0eNM0lg83E0W zAocPK2-K~s4dNUW_{o-C^o8ZES;A%NIq4lW@^hWgsgpK8-$*aF^!f4R-eG-{zHj+K zTNhVW491}8>IwYl$y2A!>FVBj_G}HDXb5)XloTlxZxZu9eVT-D0qP=-DJfEL+{ePz zRJm;VbHG8obonxgC@A=m4^}BsD}4@Px;^spGeKEh%EdKl%lRz#iJ{YR{|1bd0Ic=V z=ZBV2K+ySsm_^KiB>^tMgvW@PkG(iN!4W$fk}wRg_fxU0r*m*@w4T1m;DOrJdlRD!3(BVr# z7qAl+5l4EiOoT=f|hk$C&pEpnD<$l zCOg#I#nuYgSOx3u;+D9Q3gg^4duA3#<(60l z$BpRqr$JEf0AiG1D;&@XWIlE4jIpTZT3nz3>Fek%FskgGg6wE7HYeP9K5`p8tJA(` zPr&6eo;5JBqhbQdpOf}4S+ay!D#DoNM<1U!lE*XfT(g#MpT1_z8U)FyXap{Xv>9Sc z8Kd|H_@ToukFJ&Xp&0-J@)eGw>PS82t$QHgvoN> z@CmWT?Yno+!J?9Y2b{8r7o}x7vgA*mJc-Z;|5*z9wEM>6@(u5~g!T5o>Ui%o-v}2T z0;^ThOlCqzp{EGYH5I`EI|XB-MWd-L#;CJA=rR=M*I$QVmM`+~?1q zW9zs>}K{s;thaI67cH~)gWbIV) z-f|Tz9|}O!-AHqAP)lhX8kyDtS=%bNEPx6?J->QFbZR$Z9RD4cuaO86=Ai=ws{%;L zAO|^kWZFQI?V+11gJhqimY4e@L0ZyZJbTuxjt4I);!Sl@;u|KkGN?4Dhx#B%0)cD2 zhddyFu!ZhotI1EhE>P(7|py zJBPxynl%?cLT@YK|8}lT@$%vXih+z-CGiciHVLba8_260Ctsi<3bF3``N3bz^kPMV zFv-1;f7?OgiqO5x)@7 z(j7Oqg$Un+u~Tq0l8Pi69&b%YlB}&8+22(^@z#x;H3!{t-=d0R^_BAs0$nsj-a#L! zJ{#ScoQS(y)FK^jAo0QFS84?GiW-~g+1g6nx^;`B-$>g#p%Jk7-uYIYG4le3ofI$p&xa5bzG9kk%lN#p+0{5=8iq7d^olZ=N3PBN9Qj zD*Sbj1p&&adSEENb7#H2C>k@l4gg@-t`sPdAAaw_gIrOk5yJ^DEE3^w; zYAO;4IFFag%g_HN)nXqg+X8Gg7sNk>U<1k0MUBe6k)L(Pq-*QzXQSQvN(E;{1h-Hs zenT^najn$C(ozS~zQ)Zqh^oI-@PnF!vF zZ1l+S7jygh^QWl&t&W0=l}Kx^g8fllRh5do<|Kk5K1KgjSYrgEt(D355J`ZDv{qR7 zI9F9qZ*K!^6nRXDSUp51S^H2gL%(6rE&yUqTlBuIN6*pko}P6(c6g(C!5xmS=Esj+ zF#N@kti*vuommJ9IxYU0W5@Ew6&t`3x)llCHu?;3sNta9pspJ=Sm&A-v8ipa!)np7 z{6njjQHMWpjXu?3+RkV@!McD>V%ZRAEjgJ-1e#z901@^%MH`+!Pudp<97|GZxJvYu zk$4rm0a-~6D7J{33!zS7RGIWLY)d|2>c^Viqb7yhnIfK!{a+wnPEZj3`E~&Ve;Q6CJKRr3W>bi zPEOM(h)6#I9>X{MXd6cGaM`uu;-|S~#(Y=xVk_m0ybeY&jIdXAOp$CRD4zQj_Ev8ZXD?CrSK{QURPcN!3H zvf*QD4^p72>gtZr9ZpYkeq`&vvSJl#al(6I9@`2#J3B1J%$pNY7?6XH}Gym6R;j-K_lQ6;^+SvP;o@5UL@dvFpe-&uMd3+=jxHSvk)k zf)C+4LL10EZ#chZ9B*BHg94s1!N*{mfNAh;+;|r^h|utDbab#mNhG{Askgh!XYSpL z%=OPVGW@sO(raLb2*8SgR>49pM`{1Y*r9(Ll4!4AcB_7-MlpY!wQXoZmuFwbQ&nrdx$c<`)Lq-aTy8#`tis-)}p9N>;1=KS00aLoX4A>~VCrgP5E64uS zTK7>~Tl+UUIr9P7K!S@EOUHPv`=EVg&gLI|ifW~%I&e`Cy@Qo+R0SPxP2O{7!TW@Sb9#CkSN>6%c>DHrbgUnO z4PUoz9RjWy@C)b)$c24ZjWt+C0Zg$K+o}O(<8yjCyo}9>Hz#)?YQI{B-uP`0aeQ^` zi8korEs%cjiYk@AP@y#zHrkdy$$X~QPsjiaMs#`Nh` zNYKc;{o%y34Z>{{*!2wH$}mD7)6#C?B^*I}F&e85genFQz{)X?7Ez{onb{0e{Rf^3 zF)h~dIv_?8?BX>6@7iM9cWI~DPZ=x?0a;-sNpw$y8j&A5$~qH*?#|v=e1-z$AW|Md zCj$iaQV~~iv^b&cuyDZwS!nd)q%2*&yaVyhSsk7IXl9r`Yu4wyyzsA`_9dVUyfJ54IUBr3pjktg)c8#dg5X4_pX=h<`TKH`_!;d9YN zi12a22pEbO5nH-BhCY*Mni6-jV68klr9&lS0m*E!=`lcTpHHVZ0ALA4r(S=cGd}VT zF+X}wFcd%s$^yk@Wu1NPTF^>6aNt1dr%xnrga+y%y!Q8YOc9ie5aUnSy?ZwrO&_33 z-_~W!{8cG&_CbS#AMM+eGyNTPYVR-Y(iy^epK!7G?P?g33vhC_014R2nWcEcNJzLg z9(&>V%7_g^u`|(v*+Q7}*jkKEV zHsZUB-=19|ZgC&sS|nm>Ji%@dXt10FnTJQZM0DOu%79pMzE2Je2CeC~P;7+* zbVAK)9^z#{V#QPkX~aAsfK&V>tX%QJ-lAdvY@4`s_$ai=!bk(a%?p2CjO%PCSPWcm z(W0e2ot-GneuWMXI&re`*;%!7WW{WH_;Ja7_y=UgXj^xVixUMhhCWk=ki#MXH=rsW_j?$ zXsu;^LMhK%!fG1?%+H`)cyar39NPou&iz4%PXq%85O$NB1_|J2rpDAkn=+85$jc+u z$&DLdeYHQl(F0+-FRU%_tIRj5Q6~X^qTM`~tF6N8IuqFy$?L#lL=>C`B;P9LMz3Gs zGECDU#s$6U&m}{lld-rj3EIy==n2EoiU({A8nGJHo)iyI@{vU~*t}HQesBjU$AChY zB9y?|&zL>?3)BL>>c>HlM&4_4F36hi0Zt;@szuD46C}-pkQ&k$1QR2h0Lb-rqw@D( z^}BIEPK2vv0#XHzaSqzBrCNT#-*H~Q;KU&MBn52Ss|=VE2)KR3-n?|_QWbg6I2Wkl zJwRUk0~Jm5knKSIZTGENAD|SDCPW;nTr8O`3>;9r5M-QaNv{piF&T@BWa|6uRsv>_ zVm1o_i^Ul#oH20X#o?P_YeWIhM1(dK|vBh-VJQwk(b8~1xW=U zc@;v-A8BHBmp}g~PG$Wftb4#A3FLqsXlQrFTgC8My3Fyg*!-zt@!B4#=W9=dJpIy z&MA{NcN=wff7!Yp_+xG-v1`feQa%RFcsfs2h9bC+)$3sJbQj?%c zI0wKF+!eWVXXm*t;sN_AAAf@v&IgbIGl3*B80j74sJmj)m5&+!azfjoUQ{NEby;|O z_w3rzK7V%c@Hqandq|R&(-cUDZ`610gxxc1DTNmazv$2d6}b|xm>6N$w;C84P)N|! zsCgL1m#&eB^3KKVi*(>bsLq@Nn-e}7_IpZdP7Z0lGJyyXUE?(Xl%sJ*>d3ERCUJHU zOb2ERiO2!0f!Vdzr0w(i5POvNUL#-6Y(r(Dl#&1>1L?(gDN~#{Vj4?(-=YMl&y*WC z?ljl-GNcl?Y(Hdg0QTpr_F`(=v;2!N0Ff4|+Snw~p8-@6dw^N1K_KSHx0W%EP01Ch?|#(t!h22m(qO(wzf8aJC4H7;-cd5jp(t z8N5!M59-%C!KQC77lO*cM`Q!8PpuOVg+@5szC2ir(b*bFS9V>zh*pcu{o%O3uRgu+ zAM1Nqs&^u4!T!#{Iax0sBfwO8a6GE-Z1NNqfdhp(#|1O zd>7WMyHwu;AV48aknMm_4l|YK!VFJ?4Tf~0Ba*hf_J-P8;=h3Rajp`b^!|{~Hj0D; zOHIeYDjSj7`Xf(o+K^1pu6~o{BU(~ z=p03Nei}xl2P=S=UNa@|B($_ain(^?CRCs&lXC@= z6cE1$>QV(aVRI?*S04kR(VaH5tJU^7!XQ8-0atDCmwx+RJCCR+Arx08nM_+>pDUMy zk`F11A(p^aotB!F^(xhZO_Vbr7Az$l~+`sqf zO%;sLX4*q9erMfv*rZQ*HnSe(6?kWA31)IkQ$%6tT1WY^&d<63yS-pWTT9f_x9{Ga zMbuBy`Y>tHh^A>WeDea`-QA7f?mF8LAR4-*cIuAKzVDu- zw8QBF47}mxrE)a_l0L%VAgn`747UPA7Eb-e~25$=KES$xI65Go2O?_Y0JGD#OyGH zi(#zsj{9(+J6k>#7cbS<*N6GvDk72wgPOu4 z@mYpnyt!F$W6UY^quZEm6Q2NG(GLho%(vOxBg7%Nj!uvQD5T&8aqq3d!e5GtJh505 zLc$5SpZjqzP-@dFbb7ZV*MXCQ!tDicipoLzdC-d2M4QX$r`EVO-0(C<6z9MUvmjbC#+t;>p^Sv62V}c1c@f3zzudam$-TD zccrRrQzYM9-F5-JgSKdd;y)EV9jp$=36lU!|A0142##gx0vd6EYVTbHse`WY4^>S~ z*?5~XD^hllOdDe$KVT7Gs79>=UZL~x#*9+Rv0Vm+hW!}xuuQ~YDJ&O3Zb3f-Ui%XT zendPWINFF2j`1Z%t_NBo3lSr@re~xa&n5a03G!CZ*I*2fU4(-T0yhe9`YK7um53n> zEiND#^BF0X?R|Ny0I{B$x;lrt;?|9w-oCy^iHKPpUe~9zw0&I1>Mdt20yi%q_M3&z zw)2E$MS%}Ej{`J_`y-`u7?0$wCx;UBPo4}~{2gF#ANs*a(Fbo7TcEnRd9c(F`^3Dp z1%SIA9d+S*s819C3IJPUX3I1R_(MqYvEr8N(T)Sed3}KuHB2XsPLC_5iPz?tCX%gU zs)DE)Nzbt=Q;hErCp;Hi@7B?mpL25`AS{8$IQlaNrNBja8&6>aKsShjq&;lLGPoN; z^F*)$Z-Bbgkuz`m&*Mg(A;&<`X9iqsf@PcHfdkWlLy^20cqw{SaMCXzrtD|68WcXC z4IT)&0>sI-UdUOBfF}**5E6AE4~3VThJh1MK}9_cCJc258q(jBk=iqGMT9>xp$2hd zfaj5=AXT@B`=}M+&WZJgM@QQ3CiwgG6WJ-N;udj6Ot!~^e8x`lmUfVW+umzbSW@DH zt$YnI76s(fmY9V*qHtJ}sZDrIFe>cn~1ZflxLV+uh&aA1>kZhYu5Ak72J5 z0vAN37krT=?}L)0>pSJIq9lJA#9Rva61z}ShvkLWJc5QJ$R`3C%<46TNkXdl2TE7~ z*{rsWAiY&V;s^E*niJ9F&uMAf5F3MC@ZP*>38>*N4~G*;Ecge_a}OD)sz37el?OsL z^ZqH$C7CDIFCs8ABywAl_Ak<^d)HlMuq|gAn)eQa~xs1$@&qh5xfrtb6U1eojSFY{|6GHqY)aCNHg{#Fh^Mb zkw$Zd2U&>jVF9-y2zruPL;*of--DpetwpLX;Cz+Gk7D)hoWhufhj0Rfi8_#PNr47& z*+H!L=<^HC@55xmAK3cX2TOK1D4$(Zl$@MQ`t9H{sQSm4AflkIvJOSu51&6zMo9t9 zm^;9dCl4tg5cbs&9eAo#dr9y@&6Vy zSWZEWjKvExR-p88m9VSEPBvZ*ekiC6s+5AA3ZJWwj~rr4R08_?`hLUr0QRL2CMPXO znw}Di=!*dGND`s3r`EiUCOcU2fLn4vY3MoXq$ddTYv#;q6wJtO-Raz2M)XoaNW@;O zMpe30Eam3BBrnP(Ma4PzLR8%3oW>T1+|XzCdST(k04h;;k`L(!n`+O{O*0Mzoo6ff zv0&9AfNFr@1WpW|we$7slkhUrK?#MdEb9AtSiRcK^A4o7B@(y;$VPC0=1vg(P-5sr zPJRYy70==`N7pY34h}v83Rx=9E4-s?<@~Z^h0D>sbX9vW#j}#R;|a1+6E@3^(5%oZ z2&P_? zz#Mh3RFK5xEKO|z#)KdaT_etb3FfU>fwBu>%SIcPEkPVdsGz7c0!KIpstO?@+~<}g z#T~@M9{~YuK+ueHL*Nn@PtP=L>SdUF6)P~h*+&=nw!^mM-tU)ymOum1;Us1lL(~{9 zWtHg9F8F)6jJjXb+qsdE5Sl*#%o_koz)bmw0^p`in<$8!OTddEa2KLA1bb1S7Lf!V zXmvbH2eL&95Z?mWG#KM}iF(3Y4h%hvKok0a)_slu`XNuNV7 z1b8D`2;|lIh|z8JU?MP_ae;MnPfe8Ysw!$!02m-1$yiv3pwXbT z$^f zU;Q1ZS>nY}fI^|TTn|w2Xuaz6yCRx#0RbzKp7CL%>CQdG&b+g1J95IqX~>CcI!XJ2 zNJ^3Op+mCw4MxI&|rf}%oeXphZdu6|ze7fg>?wb>S1+VgN zUOXrqyZ(sE?VXNy?;lC3JQJ%8w5lhRgni4G zFE2p6ZQFiN(;#ICJuV&4xvYO*^H9H1pL8_}aVNHOfp(yC{1)ZIDA zx#qk-?;K>)8jqPB+JMV7WZv>Pa5sCmpxo;XLJ@im#E=OIxeR}trvR9!vZ|^Nwmqg&(@K#|9PM9uq2@_k(lfpB$%CW$yK0 z#-pU>K!Mk+k?)bYF(x1S2P6xJ|KJ0~b1_FLObtUv#4wu-AWD`0rH?b>f863Kz$2HI zmL5BPJR~6@!HNycFZ9)`jrdKK&e)Yq;F1}RBiFH=9&(G)w<Z~=j`SX)wO2cJU+9|*fTtS%+?c6^;hE8^qr z+qc(35i$>7%^eh_e4!y z)WN!}GBQhTm8bR8o*25OgXHj#oPbzMkjmvyvhrMEnzI$OdS%3Xo~TIG!#OI!)uE2; za!f@f6s$jXuLIjl>!DbhqpZclBspI%yCow7i`ZRnBgnc22WyAw z_pQr>Vc-TE7!oymSnM-(Lio3NsaIbnB}oAP%0pVBtfJ!e{Q0`;6Z@|S^j3mm3^LyiDBg{j zC)}e;5tqa3SFgs*2!HpeuJ!n%QU0qz8F`|=d9-meC9`16PW{tw{a0f)`j-Cs;{NFt z{(Rx%Z}-nz_^U1a>%}SKpPM&kYXAD6|G{q=zq^0^=#>A(7i=3ma<)g`_o;UyM{(r; zB`$3AZ)2DL_YcoJ((b-iNhixS^udUiq6~Yy;qUpI-SJQFe|!PQ-{U{+jw(mxQrEdg zmv)~|i4Rec>OIlGo9SjKQk&M1kaReKnM6Lezh-mE>82e(D|Z zaXTB{DDf{ElXfon=w;2S_tX&I%UD^u_-<#eK(d7C=u6c7@xwB`BhQ98e1^$@K`Ku& zSOP#d){%K~5PXu@H1@mae}Fh~6>VUq#ImD)wpK3CAnJ!?uLHHs&Yc?-&u?^kD6(WZ zSHOm~nC^AR(9n>?Ok|2M&N|oY8%hUC2#*u6o(%GM@!Zyq49aWp=gDfGME!V zT-i+>`;HWDjXoEaC5fn@B5|gZ zLDU0vU}w>2ZF8Gj+`>Q+gC&zdyYLY)Jmu@_yA55(gI%H06?`@3D9|=|sAy!qOXv3E zWe&1NEWBgU%dX!3DmI*z3n=-`(P2Pq`+v-J@bX_UC$7MesBk zv+9jIqJoA9VT6^MZUdpt3vdy+4nbhBDl*XFVB=Z~R#qP7nz`}DBi`&Sxe{PjAh%)T zMm?5|vT~tRvct$*fh2J21AMA^fgPiji_y2Q)WJfA^{S9#*_Gclg;$ zJ(-ab-&Jt1(?06DL*FMJ(r&IAZ*rd7VXz$-!)Cbxq$OlkfF6ncdhyKH%hH~Cl*Slp zwH$U-awd%-UiA)e>ulitK@fN$gn7AGnj^I3?ty(g$d8UUh;+X3nI zVD~w*GZ1n_0&6b6oW8QZmQT8|Vl?cVZ*OUW$?k0yK~z=~wUW>IxU#YvX4?FL&_;0% zn%yib8u+C9<;XsP(i6S^!(4lp>vMLl7cIZEdw98715LR{dPS-I3Mt)T#^=$qC|+TY zvPC?`>-kYxt(MfpY%L(R8!%!{2~C5(entr!r~}_4Oe(D!>U{OXre8zPsrtJQhC92TEbK zKwLSX0aqv-Ya>!57a?Kj;LrWiSq;+ce)8;<0wb#q1JRNQ#KfB)_ytbZbf)g945wwA*X-JuSfA_sAVuh(V! zM2v?%4-8kVCAzQ!^Y*lXb8HZ(u)0DWtP>1HDLuaNx!*RZB)NNd2s;&{2+I#$CB{He zTt{Dx^YGa~)lBH@#~ZfwLP42imxP9gZ$=p)12zD7PLg`7gHqnJm%2=Iw;=0ZFOkEq zmD>hr9MugW1k?x6XnqvZ;lP&3z9PY;eSfxQJv!XFj7E>!bgFn`X-tz*uUM_i?w(97 zs8kb{H+e8c@s?=Yo~R&&Cxpj~nZCcW)v zB3sH9O=*7s!srIX2qYMCb@%epF+MjC=$~#%lcW!JBhM}XbO=V_)`On>nP|^6ZGM+d z>>aFAmX@=mXPHrAZsMqp__>!2wk~H91u@257=c&(uUD^0=&jD((6aT4(@G)j-kU2| z_a^5xfl5KcYI>m{h~A{FKG0Q|wmd>o%o%xEeFU^T{EXR{558G*u$t4f{RH1H-0cr%fKx*E(4V|NI$UC~Myf?ktGbcu)s-MOlq>;TYhMWS)k#jIa|HYc$eoE3BwMCxRtmH)GTPAs84Z#SUbIStl zdP*dEinYXk+*sJ*jH7IY(<*ei`ZI4PYMep5RxL4fS6&jnP2p5?0cE(gTZ`tsbq%N5 zrCljFWTd|t5WW)lRDK}pNkfk#hqOK3QL_C5pc@cYJ2L5xSFFxmxdYPbqCB6#(p1Ke zNc@>xwj=EwkXAD3o4MN)fPE35~*GSsIsd42j&a|6&>-#l~{ya-Sm>n&~4&s<2l|?Rw5ZoUL21$I# zlj4ST@ax31qPkUSs%BxyO(4-ETV@#}k58)o&S2Fv>5RoHYLAfC`r{po3pkI8&_}P> z%kI)sUoFS#`Tb1Fbs9-yO)&T+Bq>bBbLiZgBoEWHi*hPh_K6Gr zhpZ7Pr#5Qh$hJbS4!@QRC+c>zdYmWodXe`4PgdW|^eu|~);WXE--TqV5k{s3a(pw! zasu7eKPrj{f>z1H$nq z`7?YU|FZx8Gmp140p@uWM&K!y(iMAGr(IXzw8y>4+kluqMpd16PGi+v${`_z zp1!^*!nigIh66LsA$zsc3bLE$cAX@ifEbnJHalZ4dS~T|bft$#Mf}%R? zavI1nCOKQvYXKN3?%w@rf+P#nMG`K%1|z0DzK7xb%NracKZKycFm8zzy%?xu3kwoe zHiY`MJ1l4eYUi}DxJtsds(&C_nE^&wM;e!LE&eYDG-%dU=bBF!!8pPNU3p_-Vn|?k z&p{wMP3z$(C|64fXgR%p5iy|+7@U+R#}|Nt^K)%LacwKQRYfcpp=qXAj95=R~dDt=CNvm<%n+X6}=^%m9W zi%0ActT)I!Oo2l<7%BM}b4M$-QWb!k^jw*0jQ?09(g>18oP;KZPMN}0o`--OiN6YA zf$sB2OMw=igUKr1u(oq;SMZ!5>$dwB_TZ_#2MiyiuNcb;r}?7j@El;yhRvJvW{Z}) zpq*G7lpEv;yThrh0reiijT^HN%#zSkG(8qmkpx2nt@X(2@-9OjK_ElGRfJm6pGajU zg1koNyJupaH9>g<5Q~Yme_lx9!Zpqk+c38P3~hb1O}HTb0~vsfGgg9bdC!19p59H& zv_7d#3N33T&z3zcp-qSZI*W$Y;*u zb>KKRKF*s7RZn1y>CO2q8t4WM@C@0EY11gPWBgq~b(&3jA4xO9)&RH1gp)jQLuZMA_qvh_?N~)fkyBBfEO{Si8oV2-7@a! z(?38p2Gu_Vb`J1 zYIHn(1?G?uf95!8FA5l)sgvfCq_7`xkt?dJI*{uUgOEQtkdg_`QF%Kzd1=~PrTo4r zs12|Haj=D+o}NjrFrBxA+a7c3O1F!M)VHx&ZEn11FUk(EtQ@X509{7gNN&(jNl;z7 zH0v^Yh(W`)5g?%}A`bSGytaTJaUp00F?MY^xE>ydSMDD5LaVrQJUYs7&kSQ{OLWDM z?q@`8^01_^2_{9VI>NM`+_G?GNeHE3Bl~J1M3i$Sh(9uoT&6kl0Q5dtv${3e!oV{d z!8*v3=*+RRSizJ>yQPM8V+e1Vmwe^cRsmIgiTYk;kS^k`IdE4!gHnXfJjW$3|661h z+1G_Lz$*AA)${j!c-G25{XQfI9T%=ZH7IMNArf_h;ED;(ldOMMCw2 z>@kYK2bO>PX@e`HG36=IF);6+0;hkCNd6hk;gKDkLvZA;58sZ9=aAKF_{!-cL*sQT zf~Yg8lvF0GV>M+MZVdk7P`?oY#0A!HocsAGjgmTHP?~o4-cZ8u72XQM4z$IoQbTTW zQZj4?oul9n+&u@(hoA6_CI&=eMvI*3V1?I&V!n8aOr_vNFAtCNsN)fo|5sB}6iD4k z-9@K2ESfoW7$jfSHq@_&!wn1TMLY}YE?9hqakeA}?#+gE^eCnS;9FLhecc6gNgi$- zQx<)J*^j);Rc-8L`uI+i?fkHqp*YR3?7AAQ#C`leGAIk{zV!mCk`QT!_tco-SaD#fyL$Dyez4J#f$Byuzh}O4jdQygbQ%HD4 zjq6LSX~I~oNy9JAW$uz|#A@~>vt@^0o6M=&hBx@~RTGE*J=&y5ZxnC+GdTXA!^Fow z_n+Mx!%IpTzxa4M^5+H`$9ex|!~QiL_?yrD_2U1t1&pyb^*0wEzp3#%|C@{d`MJL) zM1S+Se|qBahxymbFn;HMmpJ{8Jn^5O``@z+|N4plgUfJW{E`1@vBrPtpKg78MgL|e zjDPOmFVY1+9Q#YQv5LjcRr&pHwru>wEkfi7G3!Ce< zPMQ7M&3D}b)?|-u$BwE|KHNTjxX35IzSoJ-v{UHQtenRaulF)rMBn;4!lPY;&G6WvJN{fsmt<5ojYhp8D;PZ!0%G}UY0s<5{zW=d6k+SqRu zSU+nUP@FL7H%tAxq^?A>$oWclzJ!#f(pYpR zWo}^H;t}OzrF|Y@hiOOM{qQBDti18eJI<<#AivWKw76Ki6x-i@Sz3OnI!>%T?&Yen zmnQNcwd`25q2f=&zRC_7#qY%Aa4rjuo3hDw7zwMd*wsXgE=-BpCt2nExYib6)(>tm zJQdj!U3#@Q{7hhPwpWAPJ|VPvc}2W4d~*1t{@?@5dmnjbqtZCB{5W8uU6%p>M0wgx zUBx9anXX**9jr;t*1J^2eoUYyat4NnbR3P(c&%@UZ_r_p5JSqp+~B zD)hjCTKW1@GNnsge6^vD$KRep-ojdpbVC3R-Ak#$=G^wEmiSJVc zQ|qbG4q6l5$J17i9cucXr4&D%E<4e2{~BF+e9agkF!CkK#KDk0fD8K#s0ZmYmmKf; z^*uX(7^-kINKjUzeEqGnA9!|h*+#&O!uv;vos`FmUz-ghHd4G+lyku

-ZZF}2&0dyTfeHG1VxJoZ)&u~SsmQN;u%#Z`Xd zoGD`JV0~eq6uZXBFNr-kpQ%02{H4F`opMo<=^l|&ed;L95b6OYGsNY0Hf@SXas{__ zEo$>QnS4S1VX>tf!66<#LLIwK74%oe*O+GBduDoqwx;uo2A_e5rdOM7gSd~lr4bng zF5Jp23}U&Hq5(>FwfoAbU?`9}4$2cO*Ou6_JO^Hp+YUKu?!5h#s?&ngEdsbIE_~f@ zk+V{hClJMOTQ=RTWAOeIF7qHZYR7e?yag6VQG5$}H%x}Q=?&$kwA|P-qq2=D>*`Vj zhOL34rG9zRo%q@>`7afqp&~40DMCsBAgK_Z0O(f{kl5xW1}?1tVnD~nNiq@J6e|1>m*XjA~N{| z!Hs%q?AnYMr;7FtP&OWdra$IPBjc4Opm}4fW}tk4NTKlTGoQI zEeAB-w8ETDnquRBf8^*S3>@&u46s>5v)&0Tn(5clluU;Xjw_@{%N(*4QWZoiqVqo) zo8}!6zFBJ99_n15GDEN~c(=4`guV4)*I@qhT(;6Yfb>CEc&}Ops85s>6VUqS=`hr! zNmzHI`+SujGq}www8Hx%`(BD>MP0CPEMb)su9Di7A2){yW`v#r@?7Tp{VN_3&I6%0 zU7Ny=o7@bM4Z@nY9jxJ@p%UbI+9*C`(IJTN2BC&h3vNpW#O?xydddU|wIKQBj~Gc7 zenP57o+vmq)bOyckkSbX5QD#+q;t@o6@?OCgSXa0-TZ=F!1RQ;Wtuu+8mTDs|5Bm) zf>0#VZ!OmWdH?GySCbdLlfi%BDFcxz@9T3u{g^feEn zd07bJleSH#8CmF(@ar!BqD6>;d5^|3T+CLkxak9}KV~RP&bto=7_?efZ&By~33kg&sjUn3R zjXHCf#uZ8O384Pc;{Ob{tJk~#^PPsSkYuNLd8y59MDp^|&7L(KzGQUeKrmOW9K zBfsIE;-vEC* z)TH*&{(AC2SXf{46{N+1kl;vVw>6G-l4`#Z_zeXO8fK_%5oR4J#%#m=41xue4JH)hO~Wv25*@*0(lz-i z=zse%z(F8kLROBi;tX?Sy~qr6CN0aYp$7EVJgj#DNn@< zY~1`7$fE?I6JjIkur%nUbr$dhqZ@veadP*GLnFSMQnKP%+s#6jO+db=9CWRTrY9%n;IYxUa3sUjHi)BvOK=E=p%cDAxTzfu6+MMzDu1N_9hR+iV!wXd)_m)1Uz#6I*VQO{k?#D=ji`oC9c}&gAZ#lPmTR23m{Q zdEOGkxQa-aUau4j{X}Y?RRD-(B{-yop!)iw(og7V(BS#j+*XExKs(S+EAL7=EJ!UD z)P^GKaze2oBsnlWln}2Jpw3G^NJ{I3*B37^4fEIR(*{9l zn9vGRoprD1VDIx6*^IMh6bk1lxjnm%6*#XO4p|<*5Q*upjt;83ZN#j1**vtoqW;lM zWs@J#Y()jbv)DdXI?E$}#26ZbkWL!UUUJ2!^yeBA2?s*piG(etm)XN>^V^lf-+ud5oMT#=q{0u2wZhaq9FrRQPr7>&ot5(^GPWc^qv?1MktI zhj!yEADuuSKjRcJD3#GbcOXnC_ov1cAq~pD=fdxdI*jP$)SzB%ZtvbbtS#)!!QTBT zF1zDi^XRamt9<->KPnwnKAj;Z>74Z?S@C3pDXWY+bef!e@XtaZAt>jHVNy~srI%c5 zL6j2Q>(SBCK}as7d$35K?bq3N?)RpFJj)a}Y}9&$j|U080l{D>IIyZgFWi%a>P}lG z#Q$g#j+HQ~c$pc~qSWpiWgchDe?Ztg;H$Y^cu;?iPRC0nyN$$=5~?<7p(EV>gPN~n zbn`A77^qfxmNSYQO1^0=7oXZ>%}V1|RZlE+9}4WD!B(byi|=GBER_a@i1Zv1Y6RL**C*s39*6Q2Kj85QI;B9I%_;eUk1{?*FR^a%=O zZxu~nS!~u7ww|$L?G+p#B}iK~*+t-xh+uba+{;MwwA@^J?w-Ie1+!83NappW?@mhD zIyST96bu#e>|1>$Qhv5sk@wkSN)LV>`&>0!JapIgc$~uy;)Y2(AfZY_FrR<4KFI18a0QCcW8nL)a zYkf2q`q?Sa(05!iV)*f`?dbmpkoY+<>n+1L*2Wk8$ zikAj&Lka%Pln_SH6+l?JB=-3hux=${Kl_2gp!yiPMmC9}!i?=UVJ&g2s2y3GNPixQ zCo!*Xm?RJ0ROgeqcIz|6&zh6o&ZH}spRFFM;7)&|wV?0DLd6VZChmkd3dxiBdS88X ztkVm+@7Z+!@_;z#IxC(2`h@flF&Z_t#sYmZ^*Yi_Rl?i`+|htw$o!#XPchllUxKpt zOAq8XgS`fVb&4CbA#2G#AS7u)G)?E)KZKn+4^m_|#LXVWN(_n|5BuFa~YxLs<+3w=vg~g-b-`iWpJuEdv_Sg9@Uxnp~(bzk5 zBzvD7`+lOKQ=F&p>Ce+X-aky936>Pn=B#P`LK`6c; z17bQIByS>VSCod19XHz}!geiq+O~v5O(J{Jz+y_SeNb}%gMoY^i|F9C5qH{CY12l? z|Ad6s1Mf~!R|Tnhm)&zRRdoV9I5xjug_i6b!kHORIcu4cl#s*~Cf|;7`wRV%p8n8#q#q@ z5B`|zH`E7RiaLa3x%8BtmW`%;Vs*#}$pmC+{6mDmD`Y?x&k6lQ$% z!{aZ6Irw9W7_%AsKdd@QJr)5FeV}~`S0x&^R}RB9eH7g*Enlmt_u7CzBE&gpd{cHc zkUzXUMfAZVONay~5^TA1Vf1Bo5q9kFiQ5Lc8GLg0`0TFc(r5Q7WSVOQX7=XoTqPOX zRQ@JCNA>p!R@3wL_%pRq{e$m@MEaztIC}A&dE$kxa=!^fI1#3S8LI>Y@F4nPNFW7X z-a4>%UcPv70Ki>VcD6D>?()pq?Jf_z|FOs<#Q5My%(B6Jcf?PQMJ<(42VHBaM#~x1 z>Gsid3$F7)(jAYSYlGbsvq)bVnrq+ai?t{@3>3EMcRjeHdHc>VBE5>o{SKr0TU(lM z-;|44%IrR!K-(V=E*NWiMh-FqB@5zcJzVBg=+XEincjhQR@d6_n;b2Y(h-Isx|XSJ z%nl93!I{)i=pXfo-xHJpC4!memG=o*dI?fD0%v{b`2ZRe`SW|i_urgDls7?$dkLY` zFSkLt_23AZe!lsHi{BgU#`#@g;)_O2t&4(9X*TZ`$DnCjN3lzaOYY7EqyI{wg!zvK z2>oaHC(TT)w_!z;7mLG>`k;^NWpor)vLVpM2tp<;n?_(a%4eSLH%u{+fzQSWVSm|e z13``iF{dgxwGpWIe-X5QgkAL2l5hC7bsGhHxDpvR1XHJVHhdZIXN&fIHpY^55ZdB! zM2EZ_4IL5ha!KY-5)9Z@8icAy8w}Yr$r{G5!(y`t?%{0LtW^RLgD6B|i1EbdO8k}q zud3)x!_w^+59jS{dPCYoK<<)rMIN`7$PidknH9X5L_mT!6Fb&UbeJUNQa^Z(8XId7 zK<(0Gpx?S_PKtnJukr8SHsG}C08bE^r8j1s;IOkj5;yo(y8x02GpbwnN~8yYfPaB#&e{ zum>JJ?Zj0MH@+f1wD+Hi-GBrD&HV@BG~RMal-B^Ky8Iv=f|tJewsyaaAR(X|;) zPptc!aum!$!*>i>J@nJy+5_o!cgdJ-js>ZMS&c zUE(mozlW|qIVzoN}Pu3pj8YZ$?5ZwkYtXL#ch zz1Lxp$n8Rqmdd%393L$sro;TwOwwitVdhJN_5JK9uaz@BB z2nYdhXyeKYzdC|ds&;jqt8^kPcAkCMg-?hZ9x55+zZ!f7(jA|1n+X{0j zgn-~3Jq&)ZoGUT&kPym5SZ9K4MjMTzBn?;|ATx?uN2+Q}OA#wG_md|hQt*#^f{SeF=DyBzZDfdhv6Z(Lub3Qg9PdBzzGh3^4NLwWZ`^}qRc~xjA59> zc3{y4T`~9Q6YU9~8x4vCVTm93^|}yxzPxg1AtU6-9ADv0*fol-7`PL@eSQEh06}F} zZ7`PmnGALyX>SGJS|HGN_=#7AowI64$d=&rVj1*JO|2L2E-UWG+$`4Y<@TKXCtZMp zZv*vS3wf?9FHx3QUZ&u^<;fy@s5O{=S*+sz{LJZJVBpg5zWu;+-oyj^CJG41d1&hs zNX`^M5m~N7fELKcFTYarpqo3f=An2>6RjVi*Fjf^Kdwj73%UtfuEEIHB+4uC2rIv?B}Y)kH%$*K8^Lc3C~j}Z6Vje?ThYl3El5=& zf>6hZ;v@`{gV-gnAWCfu;cQ86DlkO}PK=x>Kugv2@OZ2X5t%T1mY7cke2EtUy8jph z`wytmAVxBQmG6#kiQ7bsBp{|#bqLsRS^qz|G6IG0Hh- z2*b@8>3YLz&#f*|Nox~L^&hamO=}HfYj22{aid{QB}eU2q=D=>7c<>gKs%CgL91V} zwA%I+kz*9*DEe>I5v0c)%D2}Hrb_?Lw(+#0F&!>b+cTKaZ8qALetSB&83Zq#`QixL8AUV--zqrok4IISA61Z9+VKr1UbhtE9zILu8$6!Jqdp0KGA__ z=}6cDAxYX!UCT@4r-r0s0T%`=4;QF2Lzoo}!96VYbN}Q%mQ54Y3!OuDb3lx)Zs7x_ zs70O%PVLJ2*{8oRLbnXrZU4%cOIXiYXk?F@j+ED>XL}gt3aO??V#LR-`h}eY9@^-zjGYip& zl$92ecse+2%U6XNDp~habH)%0KJZa+Lwu4gWDuq)o50yW+7Ony-W{c@#69_jzs^By zbhT260*@9dw_Rw%=ZKlBmAFicR@o&FO~Kqvcla?uQ8Pcbu`cm~Qr+e?Mx~JzKHN0f zX0JhpH;67)W@z=Vouk^=Fa2EyIrcwGJ>LxPhy{K=3P!e(H?BZ=m6troVvqW^v9y4D z3*8LIYpRSHmLNQ))K%>7Y)CpeEM=^}{KU}U9E)~t&SabD5)sDDxQL_i;GA>g4z>Lw(1i=oX>Iqsi%*P17&k)^&QBkrx?1cyLvZ#!WYLb}PxN|2pCcE^WTJq*}pZOt#3w zLduHbGJPVY>7l|uqWs>zVfn@D+b*2y`*W$Q-MA^s?Zw<3s|%W=3fp`8HEZ7e>m>eQ z`l{Kp*)NOpuTJn>YAJZ+xkBOa&~<2RZ${RFLC(=pTx(6mbjz}Xw&nb#uS?Z0q{AZ? zwK?74KB^A8JtO>7Rvmq}n}>f<+1{YwT92HFK@;!M!=JFX%ql3=q3OUbQPbPMTR16` uHPZ50)E({ck8C%roN#!A-;TdRWUn1-8vLIKDkr|8sPS0qwMMo&X#fA)xZc Date: Mon, 1 Jul 2024 08:41:47 +0200 Subject: [PATCH 012/393] fix(apidocs): move type AsyncApiResolver and config to AsyncApiDefinition component Signed-off-by: Simon Stamm --- .../AsyncApiDefinition.tsx | 29 ++++++++++++------- .../AsyncApiDefinitionWidget.tsx | 9 +----- .../AsyncApiDefinitionWidget/index.ts | 6 ++-- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx index 92052d9f6a..4817774182 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -19,7 +19,14 @@ import '@asyncapi/react-component/styles/default.css'; import { makeStyles, alpha, darken } from '@material-ui/core/styles'; import React from 'react'; import { useTheme } from '@material-ui/core/styles'; -import { AsyncApiResolver } from './AsyncApiDefinitionWidget'; + +/** @public */ +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; +}; const useStyles = makeStyles(theme => ({ root: { @@ -164,16 +171,6 @@ const httpFetchResolver: AsyncApiResolver = { }, }; -const config = { - parserOptions: { - __unstable: { - resolver: { - resolvers: [httpsFetchResolver, httpFetchResolver], - }, - }, - }, -}; - type Props = { definition: string; resolvers?: AsyncApiResolver[]; @@ -189,6 +186,16 @@ export const AsyncApiDefinition = ({ theme.palette.type === 'dark' ? classes.dark : '' }`; + const config = { + parserOptions: { + __unstable: { + resolver: { + resolvers: [httpsFetchResolver, httpFetchResolver], + }, + }, + }, + }; + // Overwrite default resolvers if custom ones are set if (resolvers) { config.parserOptions.__unstable.resolver.resolvers = resolvers; diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx index 7089143610..104a55e576 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx @@ -16,6 +16,7 @@ import { Progress } from '@backstage/core-components'; import React, { Suspense } from 'react'; +import { AsyncApiResolver } from './AsyncApiDefinition'; // The asyncapi component and related CSS has a significant size, only load it // if the element is actually used. @@ -25,14 +26,6 @@ const LazyAsyncApiDefinition = React.lazy(() => })), ); -/** @public */ -export type AsyncApiResolver = { - schema: string; - order: number; - canRead: boolean; - read(uri: any): Promise; -}; - /** @public */ export type AsyncApiDefinitionWidgetProps = { definition: string; diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts index 116eb81295..3b07c32cfe 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts @@ -15,7 +15,5 @@ */ export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; -export type { - AsyncApiDefinitionWidgetProps, - AsyncApiResolver, -} from './AsyncApiDefinitionWidget'; +export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget'; +export type { AsyncApiResolver } from './AsyncApiDefinition'; From e2d34cfcdcf41900d2351b20dcc370082e2cfb88 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 8 Jul 2024 13:04:33 +0200 Subject: [PATCH 013/393] Prioritize user documentation over README (notifications, signals) Selected content of notifications and signals README files has been moved upwards to the user documentation. Signed-off-by: Marek Libra --- docs/notifications/getting-started.md | 80 ++++++++++++++++++++----- plugins/notifications-backend/README.md | 58 ++---------------- plugins/notifications/README.md | 8 ++- 3 files changed, 78 insertions(+), 68 deletions(-) diff --git a/docs/notifications/getting-started.md b/docs/notifications/getting-started.md index 6a7ea6314e..583be64cc8 100644 --- a/docs/notifications/getting-started.md +++ b/docs/notifications/getting-started.md @@ -51,6 +51,8 @@ All parametrization is done through component properties, such as the `Notificat ![Notifications Page](notificationsPage.png) +In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/docs/reference/plugin-notifications.notificationssidebaritem) of the `` per specific needs. + ## Use New notifications can be sent either by a backend plugin or an external service through the REST API. @@ -134,12 +136,70 @@ notificationsApi.getNotification(yourId); notificationsApi.getNotification(lastSignal.notification_id); ``` +### Extending Notifications via Processors + +The notifications can be extended with `NotificationProcessor`. These processors allow to decorate notifications before they are sent or/and send the notifications to external services. + +Depending on the needs, a processor can modify the content of a notification or route it to different systems like email, Slack, or other services. + +A good example of how to write a processor is the [Email Processor](https://github.com/backstage/backstage/tree/master/plugins/notifications-backend-module-email). + +Start off by creating a notification processor: + +```ts +import { Notification } from '@backstage/plugin-notifications-common'; +import { NotificationProcessor } from '@backstage/plugin-notifications-node'; + +class MyNotificationProcessor implements NotificationProcessor { + async decorate(notification: Notification): Promise { + if (notification.origin === 'plugin-my-plugin') { + notification.payload.icon = 'my-icon'; + } + return notification; + } + + async send(notification: Notification): Promise { + nodemailer.sendEmail({ + from: 'backstage', + to: 'user', + subject: notification.payload.title, + text: notification.payload.description, + }); + } +} +``` + +Both of the processing functions are optional, and you can implement only one of them. + +Add the notification processor to the notification system by: + +```ts +import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; +import { Notification } from '@backstage/plugin-notifications-common'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'myPlugin', + register(env) { + env.registerInit({ + deps: { + notifications: notificationsProcessingExtensionPoint, + // ... + }, + async init({ notifications }) { + // ... + notifications.addProcessor(new MyNotificationProcessor()); + }, + }); + }, +}); +``` + ### External Services When the emitter of a notification is a Backstage backend plugin, it is mandatory to use the integration via `@backstage/plugin-notifications-node` as described above. If the emitter is a service external to Backstage, an HTTP POST request can be issued directly to the API, assuming that authentication is properly configured. -Refer to the service-to-service auth documentation for more details, focusing on the Static Tokens section for the simplest setup option. +Refer to the [service-to-service auth documentation](https://backstage.io/docs/auth/service-to-service-auth) for more details, focusing on the Static Tokens section for the simplest setup option. An example request for creating a broadcast notification might look like: @@ -149,20 +209,12 @@ curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications -H "Content-Type: app ## Additional info -Up-to-date documentation can be found in the plugins' README files: +Additional details can be found in the plugins' implementation: -- https://github.com/backstage/backstage/blob/master/plugins/notifications/README.md +- https://github.com/backstage/backstage/blob/master/plugins/notifications -- https://github.com/backstage/backstage/blob/master/plugins/notifications-backend/README.md +- https://github.com/backstage/backstage/blob/master/plugins/notifications-backend -- https://github.com/backstage/backstage/blob/master/plugins/notifications-node/README.md +- https://github.com/backstage/backstage/blob/master/plugins/notifications-node -- https://github.com/backstage/backstage/blob/master/plugins/signals-react/README.md - -## Processors - -The default backend behavior when emitting a new notification can be tweaked using processors. - -Depending on the needs, a processor can modify the content of a notification or route it to different systems like email, Slack, or other services. - -A good example of how to write a processor is the [Email Processor](https://github.com/backstage/backstage/tree/master/plugins/notifications-backend-module-email). +- https://github.com/backstage/backstage/blob/master/plugins/signals-react diff --git a/plugins/notifications-backend/README.md b/plugins/notifications-backend/README.md index 60c9a4e078..ebd129f572 100644 --- a/plugins/notifications-backend/README.md +++ b/plugins/notifications-backend/README.md @@ -4,6 +4,10 @@ Welcome to the notifications backend plugin! ## Getting started +```bash +yarn workspace backend add @backstage/notifications-backend +``` + Add the notifications to your backend: ```ts @@ -18,58 +22,8 @@ the signals plugin (`@backstage/plugin-signals-node`, `@backstage/plugin-signals ## Extending Notifications -The notifications can be extended with `NotificationProcessor`. These processors allow to decorate notifications -before they are sent or/and send the notifications to external services. - -Start off by creating a notification processor: - -```ts -import { Notification } from '@backstage/plugin-notifications-common'; -import { NotificationProcessor } from '@backstage/plugin-notifications-node'; - -class MyNotificationProcessor implements NotificationProcessor { - async decorate(notification: Notification): Promise { - if (notification.origin === 'plugin-my-plugin') { - notification.payload.icon = 'my-icon'; - } - return notification; - } - - async send(notification: Notification): Promise { - nodemailer.sendEmail({ - from: 'backstage', - to: 'user', - subject: notification.payload.title, - text: notification.payload.description, - }); - } -} -``` - -Both of the processing functions are optional, and you can implement only one of them. - -Add the notification processor to the notification system by: - -```ts -import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; -import { Notification } from '@backstage/plugin-notifications-common'; - -export const myPlugin = createBackendPlugin({ - pluginId: 'myPlugin', - register(env) { - env.registerInit({ - deps: { - notifications: notificationsProcessingExtensionPoint, - // ... - }, - async init({ notifications }) { - // ... - notifications.addProcessor(new MyNotificationProcessor()); - }, - }); - }, -}); -``` +When a notification is created, it's processing can be customized via `processors`. +Please refer [Backstage documentation](https://backstage.io/docs/notifications) for further details. ## Sending Notifications By Backend Plugins diff --git a/plugins/notifications/README.md b/plugins/notifications/README.md index 63212d2c16..32fbb8c701 100644 --- a/plugins/notifications/README.md +++ b/plugins/notifications/README.md @@ -2,13 +2,17 @@ Welcome to the notifications plugin! -_This plugin was created through the Backstage CLI_ - ## Getting started First, install the `@backstage/plugin-notifications-backend` and `@backstage/plugin-notifications-node` packages. See the documentation for installation instructions. +Then add this frontend package: + +```bash +yarn workspace app add @backstage/notifications +``` + To add the notifications main menu, add the following to your `packages/app/src/components/Root/Root.tsx`: ```tsx From db7a31b1d722928dab7cd8d41e0c71cdc8f4997d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:18:20 +0000 Subject: [PATCH 014/393] chore(deps): bump fast-loops from 1.1.3 to 1.1.4 Bumps [fast-loops](https://github.com/robinweser/fast-loops) from 1.1.3 to 1.1.4. - [Commits](https://github.com/robinweser/fast-loops/commits) --- updated-dependencies: - dependency-name: fast-loops dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f50727e805..78f28154d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27122,9 +27122,9 @@ __metadata: linkType: hard "fast-loops@npm:^1.1.3": - version: 1.1.3 - resolution: "fast-loops@npm:1.1.3" - checksum: b674378ba2ed8364ca1a00768636e88b22201c8d010fa62a8588a4cace04f90bac46714c13cf638be82b03438d2fe813600da32291fb47297a1bd7fa6cef0cee + version: 1.1.4 + resolution: "fast-loops@npm:1.1.4" + checksum: 8031a20f465ef35ac4ad98258470250636112d34f7e4efcb4ef21f3ced99df95a1ef1f0d6943df729a1e3e12a9df9319f3019df8cc1a0e0ed5a118bd72e505f9 languageName: node linkType: hard From 668fe02828dac3b9d640ec3ecfe0de632e381501 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 13 Jul 2024 16:30:03 -0500 Subject: [PATCH 015/393] [NBS] Updated Search Docs Signed-off-by: Andre Wanlin --- docs/features/search/collators.md | 108 ++++++++++++++ docs/features/search/getting-started.md | 79 +++------- docs/features/search/how-to-guides.md | 104 ------------- docs/features/search/search-engines.md | 188 +++++++++--------------- microsite/sidebars.json | 3 +- mkdocs.yml | 1 + 6 files changed, 200 insertions(+), 283 deletions(-) create mode 100644 docs/features/search/collators.md diff --git a/docs/features/search/collators.md b/docs/features/search/collators.md new file mode 100644 index 0000000000..9d68f71c32 --- /dev/null +++ b/docs/features/search/collators.md @@ -0,0 +1,108 @@ +--- +id: collators +title: Collators +description: Indexing you Backstage content with Collators +--- + +You need to be able to search something! Collators are the way to define what +can be searched. Backstage includes 2 collators out of the box for the [Catalog](#catalog) and [TechDocs](#techdocs). There's also some from the [Backstage Community](#community-collators) too! + +## Catalog + +The Catalog collator will index all the Entities in your Catalog. It is installed by default but if you need to add it manually here's how. + +First we add the plugin into your backend app: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-catalog +``` + +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +/* highlight-add-end */ + +backend.start(); +``` + +### Configuring the Catalog Collator + +The default schedule for the Catalog Collator is to run every 10 minutes, you can provide your own schedule by adding it to your config: + +```yaml title="app-config.yaml +search: + collators: + catalog: + schedule: # same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + initialDelay: { seconds: 90 } + # supports cron, ISO duration, "human duration" as used in code + frequency: { hours: 6 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` + +## TechDocs + +The TechDocs collator will index all the TechDocs in your Catalog. It is installed by default but if you need to add it manually here's how. + +First we add the plugin into your backend app: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-techdocs +``` + +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); +/* highlight-add-end */ + +backend.start(); +``` + +### Configuring the TechDocs Collator + +The default schedule for the TechDocs Collator is to run every 10 minutes, you can provide your own schedule by adding it to your config: + +```yaml title="app-config.yaml +search: + collators: + techdocs: + schedule: # same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + initialDelay: { seconds: 90 } + # supports cron, ISO duration, "human duration" as used in code + frequency: { hours: 6 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` + +## Community Collators + +Here are some of the known Search Collators available in from the Backstage Community: + +- [`@backstage/plugin-search-backend-module-explore`](https://github.com/backstage/backstage/tree/master/plugins/search-backend-module-explore): will index content from the [Explore plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/explore/plugins/explore). +- [`@backstage/plugin-search-backend-module-stack-overflow-collator`](https://github.com/backstage/backstage/tree/master/plugins/search-backend-module-stack-overflow-collator): will index content from Stack Overflow. +- [`@backstage-community/search-backend-module-adr`](https://github.com/backstage/community-plugins/tree/main/workspaces/adr/plugins/search-backend-module-adr): will index content from the [ADR plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/adr/plugins/adr). + +## Custom Collators + +To create your own collators/decorators modules, please use the [searchModuleCatalogCollator](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-catalog/src/alpha.ts#L49) as an example, we recommend that modules are separated by plugin packages (e.g. `search-backend-module-`). You can also find the available search engines and collator/decorator modules documentation in the Alpha API reports. diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index b2a074974f..c2ca2e1dce 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -133,79 +133,34 @@ For more information about using `Root.tsx`, please see Add the following plugins into your backend app: ```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-node +yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-module-pg @backstage/plugin-search-backend-module-catalog @backstage/plugin-search-backend-module-techdocs ``` -Create a `packages/backend/src/plugins/search.ts` file containing the following -code: +Then add the following lines: -```typescript -import { useHotCleanup } from '@backstage/backend-common'; -import { createRouter } from '@backstage/plugin-search-backend'; -import { - IndexBuilder, - LunrSearchEngine, -} from '@backstage/plugin-search-backend-node'; -import { PluginEnvironment } from '../types'; -import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; -import { Router } from 'express'; +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const searchEngine = new LunrSearchEngine({ - logger: env.logger, - }); - const indexBuilder = new IndexBuilder({ - logger: env.logger, - searchEngine, - }); +// Other plugins... - const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 10 }, - timeout: { minutes: 15 }, - initialDelay: { seconds: 3 }, - }); +/* highlight-add-start */ +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); - indexBuilder.addCollator({ - schedule: every10MinutesSchedule, - factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - tokenManager: env.tokenManager, - }), - }); +// search engines +backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); - const { scheduler } = await indexBuilder.build(); +// search collators +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); +/* highlight-add-end */ - scheduler.start(); - useHotCleanup(module, () => scheduler.stop()); - - return await createRouter({ - engine: indexBuilder.getSearchEngine(), - logger: env.logger, - }); -} +backend.start(); ``` -Make the following modifications to your `packages/backend/src/index.ts` file: +With the above setup Search will use the [Lunr](https://github.com/olivernn/lunr.js) in-memory Search Engine but if your have Postgres setup as your database then it will use Postgres as your Search Engine. Learn more in the [Search Engines](./search-engines.md) documentation. -Import the `plugins/search` file you created above: - -```typescript -import search from './plugins/search'; -``` - -Set up an environment for search: - -```typescript -const searchEnv = useHotMemoize(module, () => createEnv('search')); -``` - -Register the search service with the router: - -```typescript -apiRouter.use('/search', await search(searchEnv)); -``` +The above also sets up two Collators for you - Catalog and TechDocs - which will index content from these two locations so that you can easily search them. Learn more in the [Collators documentation](./collators.md). ## Customizing Search diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 9ce30da556..b0df9440f3 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -45,75 +45,6 @@ to do that in two steps. }); ``` -## How to index TechDocs documents - -The TechDocs plugin has supported integrations to Search, meaning that it -provides a default collator factory ready to be used. - -The purpose of this guide is to walk you through how to register the -[DefaultTechDocsCollatorFactory](https://github.com/backstage/backstage/blob/1adc2c7/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts) -in your App, so that you can get TechDocs documents indexed. - -If you have been through the -[Getting Started with Search guide](https://backstage.io/docs/features/search/getting-started), -you should have the `packages/backend/src/plugins/search.ts` file available. If -so, you can go ahead and follow this guide - if not, start by going through the -getting started guide. - -1. Import the `DefaultTechDocsCollatorFactory` from - `@backstage/plugin-search-backend-module-techdocs`. - - ```typescript - import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs'; - ``` - -2. If there isn't an existing schedule you'd like to run the collator on, be - sure to create it first. Something like... - - ```typescript - import { Duration } from 'luxon'; - - const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ seconds: 600 }), - timeout: Duration.fromObject({ seconds: 900 }), - initialDelay: Duration.fromObject({ seconds: 3 }), - }); - ``` - -3. Register the `DefaultTechDocsCollatorFactory` with the IndexBuilder. - - ```typescript - indexBuilder.addCollator({ - schedule: every10MinutesSchedule, - factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - logger: env.logger, - tokenManager: env.tokenManager, - }), - }); - ``` - -You should now have your TechDocs documents indexed to your search engine of -choice! - -If you want your users to be able to filter down to the techdocs type when -searching, you can update your `SearchPage.tsx` file in -`packages/app/src/components/search` by adding `techdocs` to the list of values -of the `SearchType` component. - -```tsx title="packages/app/src/components/search/SearchPage.tsx" - - - {/* ... */} - -``` - -> Check out the documentation around [integrating search into plugins](../../plugins/integrating-search-into-plugins.md#create-a-collator) for how to create your own collator. - ## How to customize fields in the Software Catalog or TechDocs index Sometimes, you might want to have the ability to control which data passes into the search index @@ -388,38 +319,3 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => ( ``` There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions). - -## How to migrate your backend installation to use Search together with the new backend system - -Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up. - -In "packages/backend/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]: - -```ts -const backend = createBackend(); -// [1] adding the search plugin to the backend -backend.add(import('@backstage/plugin-search-backend/alpha')); -// [2] (optional) the default search engine is Lunr, if you want to extend the search backend with another search engine. -backend.add( - import('@backstage/plugin-search-backend-module-elasticsearch/alpha'), -); -// [3] extending search with collator modules to start index documents, take in optional schedule parameters. -backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); - -backend.start(); -``` - -To create your own collators/decorators modules, please use the [searchModuleCatalogCollator](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-catalog/src/alpha.ts#L49) as an example, we recommend that modules are separated by plugin packages (e.g. `search-backend-module-`). You can also find the available search engines and collator/decorator modules documentation in the Alpha API reports: - -**Search engine modules** - -- Postgres [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-pg/alpha-api-report.md); -- Elasticsearch [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-elasticsearch/alpha-api-report.md). - -**Search collator/decorator modules** - -- Catalog [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-catalog/alpha-api-report.md); -- Explore [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-explore/alpha-api-report.md); -- TechDocs [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-techdocs/alpha-api-report.md). diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md index 86b6244fb6..f768a9bde8 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -4,34 +4,31 @@ title: Search Engines description: Choosing and configuring your search engine for Backstage --- -Backstage supports 3 search engines by default, an in-memory engine called Lunr, -Elasticsearch and Postgres. You can configure your own search engines by -implementing the provided interface as mentioned in the -[search backend documentation.](./getting-started.md#Backend) - -Provided search engine implementations have their own way of constructing -queries, which may be something you want to modify. Alterations to the querying -logic of a search engine can be made by providing your own implementation of a -QueryTranslator interface. This modification can be done without touching -provided search engines by using the exposed setter to set the modified query -translator into the instance. - -```typescript -const searchEngine = new LunrSearchEngine({ logger: env.logger }); -searchEngine.setTranslator(new MyNewAndBetterQueryTranslator()); -``` +Backstage supports 3 search engines by default, an in-memory engine called [Lunr](#lunr), [Postgres](#postgres) +and [Elasticsearch](#elasticsearch). ## Lunr -Lunr search engine is enabled by default for your backstage instance if you have -not done additional changes to the scaffolded app. +Lunr search engine is enabled by default for your Backstage instance if you have not done additional changes to the scaffolded app. -Lunr can be instantiated like this: +As Lunr is built into the Search backend plugin it can be added like this: -```typescript -// app/backend/src/plugins/search.ts -const searchEngine = new LunrSearchEngine({ logger: env.logger }); -const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend +``` + +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend/alpha')); +/* highlight-add-end */ + +backend.start(); ``` :::note Note @@ -45,34 +42,39 @@ other search engines instead. ## Postgres -The Postgres based search engine only requires that postgres being configured as +The Postgres based search engine only requires that Postgres being configured as the database engine for Backstage. Therefore it targets setups that want to avoid maintaining another external service like Elasticsearch. The search provides decent results and performs well with ten thousands of indexed -documents. The connection to postgres is established via the database manager +documents. The connection to Postgres is established via the database manager also used by other plugins. > **Important**: The search plugin requires at least Postgres 12! -To use the `PgSearchEngine`, make sure that you have a Postgres database -configured and make the following changes to your backend: +First we need to add the plugin: -1. Add a dependency on `@backstage/plugin-search-backend-module-pg` to your - backend's `package.json`. -2. Initialize the search engine. It is recommended to initialize it with a - fallback to the lunr search engine if you are running Backstage for - development locally with SQLite: - -```typescript -// In packages/backend/src/plugins/search.ts - -// Initialize a connection to a search engine. -const searchEngine = (await PgSearchEngine.supported(env.database)) - ? await PgSearchEngine.fromConfig(env.config, { database: env.database }) - : new LunrSearchEngine({ logger: env.logger }); +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-pg ``` -## Optional Configuration +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); +/* highlight-add-end */ + +backend.start(); +``` + +### Optional Configuration The following is an example of the optional configuration that can be applied when using Postgres as the search backend. Currently this is mostly for just the highlight feature: @@ -106,19 +108,32 @@ Backstage supports Elasticsearch (and OpenSearch) search engine connections, indexing and querying out of the box. Available configuration options enable usage of either AWS or Elastic.co hosted solutions, or a custom self-hosted solution. -Similarly to Lunr above, Elasticsearch can be set up like this: +Similarly to Postgres above, Elasticsearch can be set up as follows. -```typescript -// app/backend/src/plugins/search.ts -const searchEngine = await ElasticSearchSearchEngine.fromConfig({ - logger: env.logger, - config: env.config, -}); -const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); +First we need to add the plugin: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-elasticsearch ``` -For the engine to be available, your backend package needs a dependency on -package `@backstage/plugin-search-backend-module-elasticsearch`. +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add( + import('@backstage/plugin-search-backend-module-elasticsearch/alpha'), +); +/* highlight-add-end */ + +backend.start(); +``` Elasticsearch needs some additional configuration before it is ready to use within your instance. The configuration options are documented in the @@ -126,67 +141,12 @@ within your instance. The configuration options are documented in the The underlying functionality uses either the official Elasticsearch client version 7.x (meaning that Elasticsearch version 7 is the only one confirmed to -be supported), or the OpenSearch client, when the `aws` or `opensearch `provider +be supported), or the OpenSearch client, when the `aws` or `opensearch` provider is configured. -Should you need to create your own bespoke search experiences that require more -than just a query translator (such as faceted search or Relay pagination), you -can access the configuration of the search engine in order to create new -Elasticsearch clients. The version of the client need not be the same as one -used internally by the Elasticsearch engine plugin. For example: +### Example configurations -```typescript -import { isOpenSearchCompatible } from '@backstage/plugin-search-backend-module-elasticsearch'; -import { Client as ElasticClient } from '@elastic/elasticsearch'; -import { Client as OpenSearchClient } from '@opensearch-project/opensearch'; - -// Return an Elasticsearch client -const esClient = searchEngine.newClient(options => { - if (!isOpenSearchCompatible(options)) { - return new ElasticClient(options); - } - - throw new Error('Incompatible options'); -}); - -// Return an OpenSearch client -const osClient = searchEngine.newClient(options => { - if (isOpenSearchCompatible(options)) { - return new OpenSearchClient(options); - } - - throw new Error('Incompatible options'); -}); -``` - -#### Set custom index template - -The Elasticsearch engine gives you the ability to set a custom index template if needed. - -> Index templates define settings, mappings, and aliases that can be applied automatically to new indices. - -```typescript -// app/backend/src/plugins/search.ts -const searchEngine = await ElasticSearchSearchEngine.initialize({ - logger: env.logger, - config: env.config, -}); - -searchEngine.setIndexTemplate({ - name: '', - body: { - index_patterns: [''], - template: { - mappings: {}, - settings: {}, - }, - }, -}); -``` - -## Example configurations - -### AWS +#### AWS Using AWS hosted Elasticsearch the only configuration option needed is the URL to the Elasticsearch service. The implementation assumes that environment @@ -201,7 +161,7 @@ search: node: https://my-backstage-search-asdfqwerty.eu-west-1.es.amazonaws.com ``` -### Elastic.co +#### Elastic.co Elastic Cloud hosted Elasticsearch uses a Cloud ID to determine the instance of hosted Elasticsearch to connect to. Additionally, username and password needs to @@ -218,7 +178,7 @@ search: password: changeme ``` -### OpenSearch +#### OpenSearch OpenSearch can be self hosted for example with the [official docker image](https://hub.docker.com/r/opensearchproject/opensearch). The configuration requires only the node and authentication. @@ -232,7 +192,7 @@ search: password: changeme ``` -### Others +#### Others Other Elasticsearch instances can be connected to by using standard Elasticsearch authentication methods and exposed URL, provided that the cluster @@ -242,8 +202,6 @@ username/password or an API key. For more information how to create an API key, see [Elastic documentation on API keys](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html). -#### Configuration examples - ##### With username and password ```yaml @@ -273,8 +231,6 @@ you may get an error caused by limited `thread_pool` configuration. ( `429 Too M In this case you need to decrease the batch size to index the resources to prevent this kind of error. You can easily decrease or increase the batch size in your `app-config.yaml` using the `batchSize` option provided for Elasticsearch configuration. -#### Configuration example - **Set batch size to 100** ```yaml diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 075025afbb..2d533912f3 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -132,7 +132,8 @@ "features/search/concepts", "features/search/architecture", "features/search/search-engines", - "features/search/how-to-guides" + "features/search/how-to-guides", + "features/search/collators" ] }, { diff --git a/mkdocs.yml b/mkdocs.yml index f163005377..d9bbb94db5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -70,6 +70,7 @@ nav: - Concepts: 'features/search/concepts.md' - Search Architecture: 'features/search/architecture.md' - Search Engines: 'features/search/search-engines.md' + - Collators: 'features/search/collators.md' - HOW TO guides: 'features/search/how-to-guides.md' - TechDocs: - Overview: 'features/techdocs/README.md' From 2322291216aaa131eaa20dc29b554d5d04e5ab16 Mon Sep 17 00:00:00 2001 From: JounQin Date: Thu, 18 Jul 2024 19:32:43 +0800 Subject: [PATCH 016/393] fix: consider broadcast union with user close #25682 Signed-off-by: JounQin --- .../database/DatabaseNotificationsStore.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 3d027128ec..b23f5254f4 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -138,14 +138,14 @@ export class DatabaseNotificationsStore implements NotificationsStore { }; }; - private getBroadcastUnion = () => { + private getBroadcastUnion = (user?: string | null) => { return this.db('broadcast') - .leftJoin( - 'broadcast_user_status', - 'id', - '=', - 'broadcast_user_status.broadcast_id', - ) + .leftJoin('broadcast_user_status', function clause() { + const join = this.on('id', '=', 'broadcast_user_status.broadcast_id'); + if (user !== null && user !== undefined) { + join.andOnVal('user', '=', user); + } + }) .select(NOTIFICATION_COLUMNS); }; @@ -156,7 +156,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { const subQuery = this.db('notification') .select(NOTIFICATION_COLUMNS) - .unionAll([this.getBroadcastUnion()]) + .unionAll([this.getBroadcastUnion(user)]) .as('notifications'); const query = this.db.from(subQuery).where(q => { @@ -345,16 +345,19 @@ export class DatabaseNotificationsStore implements NotificationsStore { broadcastQuery.update({ ...updateColumns, read: undefined }), ]); - return await this.getNotification({ id }); + return await this.getNotification({ id, user: notification.user }); } - async getNotification(options: { id: string }): Promise { + async getNotification(options: { + id: string; + user?: string | null; + }): Promise { const rows = await this.db .select('*') .from( this.db('notification') .select(NOTIFICATION_COLUMNS) - .unionAll([this.getBroadcastUnion()]) + .unionAll([this.getBroadcastUnion(options.user)]) .as('notifications'), ) .where('id', options.id) From 0ce75e308d0a1913efeeb2d8add6f08067ca1709 Mon Sep 17 00:00:00 2001 From: JounQin Date: Thu, 18 Jul 2024 20:24:54 +0800 Subject: [PATCH 017/393] test: add related test cases Signed-off-by: JounQin --- .../DatabaseNotificationsStore.test.ts | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index c6df60c56e..d4e678c8f1 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -42,6 +42,7 @@ async function createStore(databaseId: TestDatabaseId) { const idOnly = (notification: Notification) => notification.id; const user = 'user:default/john.doe'; +const otherUser = 'user:default/jane.doe'; const id0 = '08e0871e-e60a-4f68-8110-5ae3513f992e'; const id1 = '01e0871e-e60a-4f68-8110-5ae3513f992e'; @@ -140,7 +141,7 @@ const testNotification7: Notification = { }; const otherUserNotification: Notification = { id: id0, - user: 'user:default/jane.doe', + user: otherUser, created: new Date(now), origin: 'plugin-test', payload: { @@ -246,6 +247,34 @@ describe.each(databases.eachSupportedId())( expect(notifications.map(idOnly)).toEqual([id2, id3, id1]); }); + it('should return correct broadcast notifications for different users', async () => { + await storage.saveNotification(testNotification1); + await storage.saveBroadcast(testNotification2); + await storage.saveNotification(testNotification3); + await storage.saveNotification(otherUserNotification); + + await storage.markRead({ ids: [id1, id2], user }); + + const notifications = await storage.getNotifications({ + user, + }); + expect(notifications.map(idOnly)).toEqual([id2, id3, id1]); + expect(notifications[1].user).toBe(user); + + let otherUserNotifications = await storage.getNotifications({ + user: otherUser, + }); + expect(otherUserNotifications.map(idOnly)).toEqual([id0, id2]); + expect(otherUserNotifications[1].user).toBeNull(); + + await storage.markRead({ ids: [id0, id2], user: otherUser }); + otherUserNotifications = await storage.getNotifications({ + user: otherUser, + }); + expect(otherUserNotifications.map(idOnly)).toEqual([id0, id2]); + expect(otherUserNotifications[1].user).toBe(otherUser); + }); + it('should allow searching for notifications', async () => { await storage.saveNotification(testNotification2); await storage.saveBroadcast(testNotification1); @@ -571,6 +600,23 @@ describe.each(databases.eachSupportedId())( const notification = await storage.getNotification({ id: id2 }); expect(notification?.id).toEqual(id2); }); + + it('should consider user for broadcast by id', async () => { + await storage.saveBroadcast(testNotification1); + + let notification = await storage.getNotification({ id: id1, user }); + expect(notification?.id).toEqual(id1); + expect(notification?.user).toBeNull(); + await storage.markRead({ ids: [id1], user }); + notification = await storage.getNotification({ id: id1, user }); + expect(notification?.user).toBe(user); + + const otherNotification = await storage.getNotification({ + id: id1, + user: otherUser, + }); + expect(otherNotification?.user).toBeNull(); + }); }); describe('markRead', () => { From 8013044b0c41f99f5d280c5fec804b66c143cd40 Mon Sep 17 00:00:00 2001 From: JounQin Date: Thu, 18 Jul 2024 20:25:35 +0800 Subject: [PATCH 018/393] docs: add changeset Signed-off-by: JounQin --- .changeset/lovely-planes-shop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lovely-planes-shop.md diff --git a/.changeset/lovely-planes-shop.md b/.changeset/lovely-planes-shop.md new file mode 100644 index 0000000000..3ee5a57e75 --- /dev/null +++ b/.changeset/lovely-planes-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +fix: consider broadcast union with user From 0f9e910d23739913ea9ea58c746791a9a3ef16ab Mon Sep 17 00:00:00 2001 From: "Jason Diaz G." Date: Wed, 3 Jul 2024 20:46:51 -0600 Subject: [PATCH 019/393] feat(cloudflare-auth-access-provider): Add support for cloudflare custom headers and cookie auth name Signed-off-by: Jason Diaz G. --- .../config.d.ts | 2 + .../src/helpers.test.ts | 79 +++++++++++++++++++ .../src/helpers.ts | 19 +++-- 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts index ff7d722d48..235cac8446 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts @@ -27,6 +27,8 @@ export interface Config { token: string; subject: string; }>; + customHeader?: string; + customCookieAuthName?: string; }; /** * The backstage token expiration. diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts index 9efab07ba5..be91583e16 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts @@ -239,4 +239,83 @@ describe('helpers', () => { token: token, }); }); + + it('works for regular tokens, through custom header auth', async () => { + jest.useFakeTimers({ + now: 1600000004000, + }); + + const helper = AuthHelper.fromConfig( + new ConfigReader({ teamName: 'mock-team', customHeader: 'X-Auth-Token' }), + { cache }, + ); + const token = await tokenFactory.userToken(); + const request = createRequest({ + headers: { ['X-Auth-Token']: token }, + }); + + const expected = { + cfIdentity: { + email: 'hello@example.com', + groups: [{ id: '123', email: 'foo@bar.com', name: 'foo' }], + id: '1234567890', + name: 'User Name', + }, + claims: { + iss: `https://mock-team.cloudflareaccess.com`, + sub: '1234567890', + name: 'User Name', + iat: 1600000000, + exp: 1600000005, + }, + expiresInSeconds: 5, + }; + + await expect(helper.authenticate(request)).resolves.toEqual({ + ...expected, + token: token, + }); + expect(cache.set).toHaveBeenCalledTimes(1); + expect(cache.set.mock.calls[0][0]).toBe( + 'providers/cloudflare-access/profile-v1/1234567890', + ); + expect(JSON.parse(cache.set.mock.calls[0][1] as string)).toEqual(expected); + }); + + it('works for regular tokens, through custom cookie auth name', async () => { + jest.useFakeTimers({ + now: 1600000004000, + }); + + const helper = AuthHelper.fromConfig( + new ConfigReader({ teamName: 'mock-team', customCookieAuthName: 'CF_Custom_Auth' }), + { cache }, + ); + const token = await tokenFactory.userToken(); + const request = createRequest({ + cookies: { 'CF_Custom_Auth': token }, + }); + + const expected = { + cfIdentity: { + email: 'hello@example.com', + groups: [{ id: '123', email: 'foo@bar.com', name: 'foo' }], + id: '1234567890', + name: 'User Name', + }, + claims: { + iss: `https://mock-team.cloudflareaccess.com`, + sub: '1234567890', + name: 'User Name', + iat: 1600000000, + exp: 1600000005, + }, + expiresInSeconds: 5, + }; + + await expect(helper.authenticate(request)).resolves.toEqual({ + ...expected, + token: token, + }); + }); }); diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index c1a14ae6b4..298fd82a89 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -40,6 +40,8 @@ export class AuthHelper { options?: { cache?: CacheService }, ): AuthHelper { const teamName = config.getString('teamName'); + const customHeader = config.getString('customHeader'); + const customCookieAuthName = config.getString('customCookieAuthName'); const serviceTokens = ( config.getOptionalConfigArray('serviceTokens') ?? [] )?.map(cfg => { @@ -53,7 +55,7 @@ export class AuthHelper { new URL(`https://${teamName}.cloudflareaccess.com/cdn-cgi/access/certs`), ); - return new AuthHelper(teamName, serviceTokens, keySet, options?.cache); + return new AuthHelper(teamName, serviceTokens, keySet, options?.cache, customHeader, customCookieAuthName); } private constructor( @@ -61,20 +63,23 @@ export class AuthHelper { private readonly serviceTokens: ServiceToken[], private readonly keySet: ReturnType, private readonly cache?: CacheService, - ) {} + private readonly customHeader?: string, + private readonly customCookieAuthName?: string, + ) { } async authenticate(req: express.Request): Promise { // JWTs generated by Access are available in a request header as // Cf-Access-Jwt-Assertion and as cookies as CF_Authorization. - let jwt = req.header(CF_JWT_HEADER); + let jwt = req.header(this.customHeader || CF_JWT_HEADER); if (!jwt) { - jwt = req.cookies.CF_Authorization; + jwt = req.cookies[this.customCookieAuthName || COOKIE_AUTH_NAME]; } if (!jwt) { // Only throw if both are not provided by Cloudflare Access since either // can be used. throw new AuthenticationError( - `Missing ${CF_JWT_HEADER} from Cloudflare Access`, + `Missing ${this.customHeader || CF_JWT_HEADER} and + ${this.customCookieAuthName || COOKIE_AUTH_NAME} from Cloudflare Access`, ); } @@ -155,8 +160,8 @@ export class AuthHelper { ): Promise { const headers = new Headers(); // set both headers just the way inbound responses are set - headers.set(CF_JWT_HEADER, jwt); - headers.set('cookie', `${COOKIE_AUTH_NAME}=${jwt}`); + headers.set(this.customHeader || CF_JWT_HEADER, jwt); + headers.set('cookie', `${this.customCookieAuthName || COOKIE_AUTH_NAME}=${jwt}`); try { const res = await fetch( `https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`, From 75d026ab1f9587a3498b23be9cd5ef63c7e5e777 Mon Sep 17 00:00:00 2001 From: "Jason Diaz G." Date: Wed, 3 Jul 2024 21:11:53 -0600 Subject: [PATCH 020/393] chore(cloudflare-auth-access-provider): Add changeset for the PR Signed-off-by: Jason Diaz G. --- .changeset/strong-chicken-kiss.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-chicken-kiss.md diff --git a/.changeset/strong-chicken-kiss.md b/.changeset/strong-chicken-kiss.md new file mode 100644 index 0000000000..26f667c5ab --- /dev/null +++ b/.changeset/strong-chicken-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': minor +--- + +Support for Cloudflare Custom Headers and Custom Cookie Auth Name From eeadefbb09b2f1a8b5aa0a77d92400d0680c9054 Mon Sep 17 00:00:00 2001 From: "Jason Diaz G." Date: Thu, 4 Jul 2024 11:15:44 -0600 Subject: [PATCH 021/393] fix(cloudflare-auth-access-provider): broken tests fix Signed-off-by: Jason Diaz G. --- .../src/helpers.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index 298fd82a89..45718ffab1 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -40,8 +40,10 @@ export class AuthHelper { options?: { cache?: CacheService }, ): AuthHelper { const teamName = config.getString('teamName'); - const customHeader = config.getString('customHeader'); - const customCookieAuthName = config.getString('customCookieAuthName'); + const customHeader = config.getOptionalString('customHeader'); + const customCookieAuthName = config.getOptionalString( + 'customCookieAuthName', + ); const serviceTokens = ( config.getOptionalConfigArray('serviceTokens') ?? [] )?.map(cfg => { @@ -55,7 +57,14 @@ export class AuthHelper { new URL(`https://${teamName}.cloudflareaccess.com/cdn-cgi/access/certs`), ); - return new AuthHelper(teamName, serviceTokens, keySet, options?.cache, customHeader, customCookieAuthName); + return new AuthHelper( + teamName, + serviceTokens, + keySet, + options?.cache, + customHeader, + customCookieAuthName, + ); } private constructor( @@ -65,7 +74,7 @@ export class AuthHelper { private readonly cache?: CacheService, private readonly customHeader?: string, private readonly customCookieAuthName?: string, - ) { } + ) {} async authenticate(req: express.Request): Promise { // JWTs generated by Access are available in a request header as @@ -79,7 +88,9 @@ export class AuthHelper { // can be used. throw new AuthenticationError( `Missing ${this.customHeader || CF_JWT_HEADER} and - ${this.customCookieAuthName || COOKIE_AUTH_NAME} from Cloudflare Access`, + ${ + this.customCookieAuthName || COOKIE_AUTH_NAME + } from Cloudflare Access`, ); } @@ -161,7 +172,10 @@ export class AuthHelper { const headers = new Headers(); // set both headers just the way inbound responses are set headers.set(this.customHeader || CF_JWT_HEADER, jwt); - headers.set('cookie', `${this.customCookieAuthName || COOKIE_AUTH_NAME}=${jwt}`); + headers.set( + 'cookie', + `${this.customCookieAuthName || COOKIE_AUTH_NAME}=${jwt}`, + ); try { const res = await fetch( `https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`, From 8a7db0dd15954f0ce50e18495f46352188625d18 Mon Sep 17 00:00:00 2001 From: "Jason Diaz G." Date: Thu, 4 Jul 2024 20:03:06 -0600 Subject: [PATCH 022/393] fix(cloudflare-auth-access-provider): change default names for custom variables Signed-off-by: Jason Diaz G. --- .../config.d.ts | 4 +-- .../src/helpers.test.ts | 16 ++++++++---- .../src/helpers.ts | 26 +++++++++---------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts index 235cac8446..9715eb3a39 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts @@ -27,8 +27,8 @@ export interface Config { token: string; subject: string; }>; - customHeader?: string; - customCookieAuthName?: string; + jwtHeaderName?: string; + authorizationCookieName?: string; }; /** * The backstage token expiration. diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts index be91583e16..d5a1c8be0f 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts @@ -240,13 +240,16 @@ describe('helpers', () => { }); }); - it('works for regular tokens, through custom header auth', async () => { + it('works for regular tokens, through jwtHeaderName header', async () => { jest.useFakeTimers({ now: 1600000004000, }); const helper = AuthHelper.fromConfig( - new ConfigReader({ teamName: 'mock-team', customHeader: 'X-Auth-Token' }), + new ConfigReader({ + teamName: 'mock-team', + jwtHeaderName: 'X-Auth-Token', + }), { cache }, ); const token = await tokenFactory.userToken(); @@ -282,18 +285,21 @@ describe('helpers', () => { expect(JSON.parse(cache.set.mock.calls[0][1] as string)).toEqual(expected); }); - it('works for regular tokens, through custom cookie auth name', async () => { + it('works for regular tokens, through authorizationCookieName cookie name', async () => { jest.useFakeTimers({ now: 1600000004000, }); const helper = AuthHelper.fromConfig( - new ConfigReader({ teamName: 'mock-team', customCookieAuthName: 'CF_Custom_Auth' }), + new ConfigReader({ + teamName: 'mock-team', + authorizationCookieName: 'CF_Auth', + }), { cache }, ); const token = await tokenFactory.userToken(); const request = createRequest({ - cookies: { 'CF_Custom_Auth': token }, + cookies: { CF_Custom_Auth: token }, }); const expected = { diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index 45718ffab1..b3fefb8789 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -40,9 +40,9 @@ export class AuthHelper { options?: { cache?: CacheService }, ): AuthHelper { const teamName = config.getString('teamName'); - const customHeader = config.getOptionalString('customHeader'); - const customCookieAuthName = config.getOptionalString( - 'customCookieAuthName', + const jwtHeaderName = config.getOptionalString('jwtHeaderName'); + const authorizationCookieName = config.getOptionalString( + 'authorizationCookieName', ); const serviceTokens = ( config.getOptionalConfigArray('serviceTokens') ?? [] @@ -62,8 +62,8 @@ export class AuthHelper { serviceTokens, keySet, options?.cache, - customHeader, - customCookieAuthName, + jwtHeaderName, + authorizationCookieName, ); } @@ -72,24 +72,24 @@ export class AuthHelper { private readonly serviceTokens: ServiceToken[], private readonly keySet: ReturnType, private readonly cache?: CacheService, - private readonly customHeader?: string, - private readonly customCookieAuthName?: string, + private readonly jwtHeaderName?: string, + private readonly authorizationCookieName?: string, ) {} async authenticate(req: express.Request): Promise { // JWTs generated by Access are available in a request header as // Cf-Access-Jwt-Assertion and as cookies as CF_Authorization. - let jwt = req.header(this.customHeader || CF_JWT_HEADER); + let jwt = req.header(this.jwtHeaderName || CF_JWT_HEADER); if (!jwt) { - jwt = req.cookies[this.customCookieAuthName || COOKIE_AUTH_NAME]; + jwt = req.cookies[this.authorizationCookieName || COOKIE_AUTH_NAME]; } if (!jwt) { // Only throw if both are not provided by Cloudflare Access since either // can be used. throw new AuthenticationError( - `Missing ${this.customHeader || CF_JWT_HEADER} and + `Missing ${this.jwtHeaderName || CF_JWT_HEADER} and ${ - this.customCookieAuthName || COOKIE_AUTH_NAME + this.authorizationCookieName || COOKIE_AUTH_NAME } from Cloudflare Access`, ); } @@ -171,10 +171,10 @@ export class AuthHelper { ): Promise { const headers = new Headers(); // set both headers just the way inbound responses are set - headers.set(this.customHeader || CF_JWT_HEADER, jwt); + headers.set(this.jwtHeaderName || CF_JWT_HEADER, jwt); headers.set( 'cookie', - `${this.customCookieAuthName || COOKIE_AUTH_NAME}=${jwt}`, + `${this.authorizationCookieName || COOKIE_AUTH_NAME}=${jwt}`, ); try { const res = await fetch( From 9614e649ed63b9fdcd29c474aa2d09f96520e834 Mon Sep 17 00:00:00 2001 From: "Jason Diaz G." Date: Fri, 5 Jul 2024 08:36:01 -0600 Subject: [PATCH 023/393] fix(cloudflare-auth-access-provider): fix broken unit tests Signed-off-by: Jason Diaz G. --- .../src/helpers.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts index d5a1c8be0f..07e9860d98 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts @@ -299,7 +299,7 @@ describe('helpers', () => { ); const token = await tokenFactory.userToken(); const request = createRequest({ - cookies: { CF_Custom_Auth: token }, + cookies: { CF_Auth: token }, }); const expected = { From 034c0b3cb29e73b9cf3598a0d65c0d216ab7ed96 Mon Sep 17 00:00:00 2001 From: "Jason Diaz G." Date: Thu, 18 Jul 2024 16:45:13 -0600 Subject: [PATCH 024/393] feat(cloudflare-auth-access-provider): Change defaults and add provider docs Signed-off-by: Jason Diaz G. --- docs/auth/cloudflare/provider.md | 6 ++++ .../src/helpers.ts | 33 ++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/auth/cloudflare/provider.md b/docs/auth/cloudflare/provider.md index 516ea9d647..a1946580a7 100644 --- a/docs/auth/cloudflare/provider.md +++ b/docs/auth/cloudflare/provider.md @@ -32,6 +32,12 @@ auth: serviceTokens: - token: '1uh2fh19efvfh129f1f919u21f2f19jf2.access' subject: 'bot-user@your-company.com' + # You can customize the header name that contains the jwt token, by default + # cf-access-jwt-assertion is used + jwtHeaderName: + # You can customize the authorization cookie name, by default + # CF_Authorization is used + authorizationCookieName: # This picks what sign in resolver(s) you want to use. signIn: resolvers: diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index b3fefb8789..3077258dd6 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -40,10 +40,10 @@ export class AuthHelper { options?: { cache?: CacheService }, ): AuthHelper { const teamName = config.getString('teamName'); - const jwtHeaderName = config.getOptionalString('jwtHeaderName'); - const authorizationCookieName = config.getOptionalString( - 'authorizationCookieName', - ); + const jwtHeaderName = + config.getOptionalString('jwtHeaderName') ?? CF_JWT_HEADER; + const authorizationCookieName = + config.getOptionalString('authorizationCookieName') ?? COOKIE_AUTH_NAME; const serviceTokens = ( config.getOptionalConfigArray('serviceTokens') ?? [] )?.map(cfg => { @@ -60,37 +60,35 @@ export class AuthHelper { return new AuthHelper( teamName, serviceTokens, - keySet, - options?.cache, jwtHeaderName, authorizationCookieName, + keySet, + options?.cache, ); } private constructor( private readonly teamName: string, private readonly serviceTokens: ServiceToken[], + private readonly jwtHeaderName: string, + private readonly authorizationCookieName: string, private readonly keySet: ReturnType, private readonly cache?: CacheService, - private readonly jwtHeaderName?: string, - private readonly authorizationCookieName?: string, ) {} async authenticate(req: express.Request): Promise { // JWTs generated by Access are available in a request header as // Cf-Access-Jwt-Assertion and as cookies as CF_Authorization. - let jwt = req.header(this.jwtHeaderName || CF_JWT_HEADER); + let jwt = req.header(this.jwtHeaderName); if (!jwt) { - jwt = req.cookies[this.authorizationCookieName || COOKIE_AUTH_NAME]; + jwt = req.cookies[this.authorizationCookieName]; } if (!jwt) { // Only throw if both are not provided by Cloudflare Access since either // can be used. throw new AuthenticationError( - `Missing ${this.jwtHeaderName || CF_JWT_HEADER} and - ${ - this.authorizationCookieName || COOKIE_AUTH_NAME - } from Cloudflare Access`, + `Missing ${this.jwtHeaderName} and + ${this.authorizationCookieName} from Cloudflare Access`, ); } @@ -171,11 +169,8 @@ export class AuthHelper { ): Promise { const headers = new Headers(); // set both headers just the way inbound responses are set - headers.set(this.jwtHeaderName || CF_JWT_HEADER, jwt); - headers.set( - 'cookie', - `${this.authorizationCookieName || COOKIE_AUTH_NAME}=${jwt}`, - ); + headers.set(this.jwtHeaderName, jwt); + headers.set('cookie', `${this.authorizationCookieName}=${jwt}`); try { const res = await fetch( `https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`, From da971316ae3412c2b0b98f9c0c94fcd8ade65bdb Mon Sep 17 00:00:00 2001 From: parmar-abhinav Date: Sun, 21 Jul 2024 14:01:44 +0530 Subject: [PATCH 025/393] Added test cases for gitlab:issues:create examples Signed-off-by: parmar-abhinav --- .changeset/funny-wolves-learn.md | 5 + .../gitlabIssueCreate.examples.test.ts | 711 ++++++++++++++++++ .../src/actions/gitlabIssueCreate.examples.ts | 168 ++++- 3 files changed, 879 insertions(+), 5 deletions(-) create mode 100644 .changeset/funny-wolves-learn.md create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts diff --git a/.changeset/funny-wolves-learn.md b/.changeset/funny-wolves-learn.md new file mode 100644 index 0000000000..2010ccd68b --- /dev/null +++ b/.changeset/funny-wolves-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Added test cases for gitlab:issues:create examples diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts new file mode 100644 index 0000000000..917ba61d91 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts @@ -0,0 +1,711 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabIssueAction } from './gitlabIssueCreate'; +import { examples } from './gitlabIssueCreate.examples'; +import yaml from 'yaml'; + +const mockGitlabClient = { + Issues: { + create: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:issues:create', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(2005, 6, 4, 11, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'sample-token', + apiBaseUrl: 'https://gitlab.com/api/v1', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabIssueAction({ integrations }); + + it(`should ${examples[0].description}`, async () => { + let input; + try { + input = yaml.parse(examples[0].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[1].description}`, async () => { + let input; + try { + input = yaml.parse(examples[1].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [18], + confidential: false, + epicId: undefined, + labels: '', + createdAt: input.createdAt, + dueDate: input.dueDate, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[2].description}`, async () => { + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + epicId: undefined, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [18, 15], + confidential: input.confidential, + epicId: undefined, + labels: input.labels, + createdAt: input.createdAt, + dueDate: input.dueDate, + discussionToResolve: input.discussionToResolve, + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[3].description}`, async () => { + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[4].description}`, async () => { + let input; + try { + input = yaml.parse(examples[4].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[5].description}`, async () => { + let input; + try { + input = yaml.parse(examples[5].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[6].description}`, async () => { + let input; + try { + input = yaml.parse(examples[6].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[7].description}`, async () => { + let input; + try { + input = yaml.parse(examples[7].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [18, 22], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[8].description}`, async () => { + let input; + try { + input = yaml.parse(examples[8].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[9].description}`, async () => { + let input; + try { + input = yaml.parse(examples[9].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[10].description}`, async () => { + let input; + try { + input = yaml.parse(examples[10].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: 'abc123', + mergeRequestToResolveDiscussionsOf: + input.mergeRequestToResolveDiscussionsOf, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts index 760caf0f9f..04fdeba46d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts @@ -50,8 +50,8 @@ export const examples: TemplateExample[] = [ title: 'Test Issue', assignees: [18], description: 'This is the description of the issue', - createdAt: '2022-09-27 18:00:00.000', - dueDate: '2022-09-28 12:00:00.000', + createdAt: '2022-09-27T18:00:00.000Z', + dueDate: '2022-09-28T12:00:00.000Z', }, }, ], @@ -72,9 +72,9 @@ export const examples: TemplateExample[] = [ assignees: [18, 15], description: 'This is the description of the issue', confidential: false, - createdAt: '2022-09-27 18:00:00.000', - dueDate: '2022-09-28 12:00:00.000', - discussionToResolve: 1, + createdAt: '2022-09-27T18:00:00.000Z', + dueDate: '2022-09-28T12:00:00.000Z', + discussionToResolve: '1', epicId: 1, labels: 'phase1:label1,phase2:label2', }, @@ -82,4 +82,162 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a GitLab issue with token', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue', + description: 'This is the description of the issue', + token: 'sample token', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue with a specific milestone and weight', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue with Milestone', + description: 'This is the description of the issue', + milestoneId: 5, + weight: 3, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type INCIDENT', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'incident', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type TEST', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'test_case', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type TASK with assignees', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'task', + assignees: [18, 22], + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type ISSUE and close it', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'issue', + stateEvent: 'close', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type INCIDENT and reopen it', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'incident', + stateEvent: 'reopen', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab issue to resolve a discussion in a merge request', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue for MR Discussion', + description: 'This is the description of the issue', + mergeRequestToResolveDiscussionsOf: 42, + discussionToResolve: 'abc123', + }, + }, + ], + }), + }, ]; From ff69e3c901d82fc8367df7bfaed1a83e0b12a202 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Mon, 22 Jul 2024 12:16:32 +0530 Subject: [PATCH 026/393] Updated writing a policy docs Signed-off-by: AmbrishRamachandiran --- docs/permissions/writing-a-policy.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index 0f2d4fe091..0762f6b0c6 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -141,4 +141,8 @@ class TestPermissionPolicy implements PermissionPolicy { In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! -_Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. +:::note Note + +Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. + +::: From 187f583ba2961dfbce660f224073d049a4bb726e Mon Sep 17 00:00:00 2001 From: parmar-abhinav Date: Mon, 22 Jul 2024 16:58:50 +0530 Subject: [PATCH 027/393] Added examples for publish:azure action and updated its test cases Signed-off-by: parmar-abhinav --- .changeset/flat-papayas-push.md | 5 + .../src/actions/azure.examples.test.ts | 177 +++++++++++++++++- .../src/actions/azure.examples.ts | 90 +++++++++ 3 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 .changeset/flat-papayas-push.md diff --git a/.changeset/flat-papayas-push.md b/.changeset/flat-papayas-push.md new file mode 100644 index 0000000000..191dad8550 --- /dev/null +++ b/.changeset/flat-papayas-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-azure': minor +--- + +Added examples for publish:azure action and updated its test cases diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts index 35868274fd..dda65a76ea 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts @@ -19,7 +19,10 @@ import { ConfigReader } from '@backstage/config'; import { createPublishAzureAction } from './azure'; import { ScmIntegrations } from '@backstage/integration'; import { WebApi } from 'azure-devops-node-api'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; +import { + getRepoSourceDirectory, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; import { examples } from './azure.examples'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -48,6 +51,10 @@ describe('publish:azure examples', () => { host: 'dev.azure.com', credentials: [{ personalAccessToken: 'tokenlols' }], }, + { + host: 'test.azure.com', + credentials: [{ personalAccessToken: 'tokenlols' }], + }, ], }, }); @@ -114,4 +121,172 @@ describe('publish:azure examples', () => { gitAuthorInfo: {}, }); }); + + it(`should ${examples[3].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: input.gitCommitMessage, + gitAuthorInfo: {}, + }); + }); + + it(`should ${examples[4].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + let input; + try { + input = yaml.parse(examples[4].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: { + name: input.gitAuthorName, + email: input.gitAuthorEmail, + }, + }); + }); + + it(`should ${examples[5].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + let input; + try { + input = yaml.parse(examples[5].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath), + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); + + it(`should ${examples[6].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + let input; + try { + input = yaml.parse(examples[6].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: input.token }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); + + it(`should ${examples[7].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://test.azure.com/organization/project/_git/repo', + webUrl: 'https://test.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + let input; + try { + input = yaml.parse(examples[7].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://test.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts index 22c2299b30..ae5bfece2b 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts @@ -70,4 +70,94 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: + 'Initializes an Azure DevOps repository with a custom commit message', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + gitCommitMessage: 'Initial setup and configuration', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository with a custom author name and email', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + gitAuthorName: 'John Doe', + gitAuthorEmail: 'john.doe@example.com', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository using a specific source path', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + sourcePath: 'path/to/source', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository using an authentication token', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + token: 'personal-access-token', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository using an custom repo url', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'test.azure.com?organization=organization&project=project&repo=repo', + }, + }, + ], + }), + }, ]; From 7681b1746e14d900d7f0e383e64c86f513f59075 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 27 Jun 2024 16:56:57 -0400 Subject: [PATCH 028/393] chore: remove PII from logs Signed-off-by: Frank Kong --- .changeset/funny-tips-push.md | 5 +++++ .github/vale/config/vocabularies/Backstage/accept.txt | 1 + .../src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts | 5 +++-- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/funny-tips-push.md diff --git a/.changeset/funny-tips-push.md b/.changeset/funny-tips-push.md new file mode 100644 index 0000000000..224e6a073a --- /dev/null +++ b/.changeset/funny-tips-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +update the `morgan` middleware to use a custom format to prevent PII from being logged diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 42b0e4895c..50da5fc20f 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -243,6 +243,7 @@ Mkdocs monorepo Monorepo monorepos +morgan msgraph msw mutex diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 77136fd7e5..f38c10820a 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -140,8 +140,9 @@ export class MiddlewareFactory { const logger = this.#logger.child({ type: 'incomingRequest', }); - - return morgan('combined', { + const customMorganFormat = + '[:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'; + return morgan(customMorganFormat, { stream: { write(message: string) { logger.info(message.trimEnd()); From 53d7e4fa2c837f9a810091c83755fb5e3a371939 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 22 Jul 2024 11:35:18 -0400 Subject: [PATCH 029/393] docs: add custom logging docs Signed-off-by: Frank Kong --- .../core-services/root-http-router.md | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index b3522e1752..88a10455f5 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -52,19 +52,49 @@ You can configure the root HTTP Router service by passing the options to the `cr ```ts import { rootHttpRouterServiceFactory } from '@backstage/backend-app-api'; +import { RequestHandler } from 'express'; +import morgan from 'morgan'; const backend = createBackend(); backend.add( rootHttpRouterServiceFactory({ configure: ({ app, middleware, routes, config, logger, lifecycle }) => { + // Refer to https://expressjs.com/en/guide/writing-middleware.html on how to write express middleware + const customMiddleware = { + logging(): RequestHandler { + const middlewareLogger = logger.child({ + type: 'incomingRequest', + }); + return (req, res, next) => { + // Custom Logging Implementation + next(); + }; + }, + // Default logging middleware uses the [morgan](https://github.com/expressjs/morgan) middleware which you can configure with custom formats. + morganLogging(): RequestHandler { + const middlewareLogger = logger.child({ + type: 'incomingRequest', + }); + const customMorganFormat = + '[:date[clf]] ":method :url HTTP/:http-version" :status ":user-agent"'; + return morgan(customMorganFormat, { + stream: { + write(message: string) { + logger.info(message.trimEnd()); + }, + }, + }); + }, + }; + // the built in middleware is provided through an option in the configure function app.use(middleware.helmet()); app.use(middleware.cors()); app.use(middleware.compression()); // you can add you your own middleware in here - app.use(custom.logging()); + app.use(customMiddleware.logging()); // here the routes that are registered by other plugins app.use(routes); From 776eb56523b29920592f4ca688d0613481f0bd72 Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Thu, 25 Jul 2024 00:52:49 +0200 Subject: [PATCH 030/393] fix(catalog-backend): error if metadata.annotations isn't a valid object Signed-off-by: Thomas Cardonne --- .changeset/cuddly-penguins-glow.md | 7 ++++ .../processing/ProcessorOutputCollector.ts | 35 ++++++++++--------- 2 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 .changeset/cuddly-penguins-glow.md diff --git a/.changeset/cuddly-penguins-glow.md b/.changeset/cuddly-penguins-glow.md new file mode 100644 index 0000000000..b00422cf8b --- /dev/null +++ b/.changeset/cuddly-penguins-glow.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +`ProcessorOutputCollector` returns an error when receiving deferred entities that have an invalid `metadata.annotations` format. + +This allows to return an error on an actual validation issue instead of reporting that the location annotations are missing afterwards, which is misleading for the users. diff --git a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts index eb8cbcc81a..0de6b12b90 100644 --- a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts @@ -115,24 +115,27 @@ export class ProcessorOutputCollector { // Note that at this point, we have only validated the envelope part of // the entity data. Annotations are not part of that, so we have to be - // defensive. If the annotations were malformed (e.g. were not a valid - // object), we just skip over this step and let the full entity - // validation at the next step of processing catch that. + // defensive and report an error if the annotations isn't a valid object, to avoid + // hiding errors when adding location annotations. const annotations = entity.metadata.annotations || {}; - if (typeof annotations === 'object' && !Array.isArray(annotations)) { - const originLocation = getEntityOriginLocationRef(this.parentEntity); - entity = { - ...entity, - metadata: { - ...entity.metadata, - annotations: { - ...annotations, - [ANNOTATION_ORIGIN_LOCATION]: originLocation, - [ANNOTATION_LOCATION]: location, - }, - }, - }; + if (typeof annotations !== 'object' || Array.isArray(annotations)) { + this.errors.push( + new Error('metadata.annotations must be a valid object'), + ); + return; } + const originLocation = getEntityOriginLocationRef(this.parentEntity); + entity = { + ...entity, + metadata: { + ...entity.metadata, + annotations: { + ...annotations, + [ANNOTATION_ORIGIN_LOCATION]: originLocation, + [ANNOTATION_LOCATION]: location, + }, + }, + }; this.deferredEntities.push({ entity, locationKey: location }); } else if (i.type === 'location') { From b0bee161ee1213af097865281f1ec01b3100cd82 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 25 Jul 2024 11:44:43 +0200 Subject: [PATCH 031/393] feat(MyGroupsPicker): use entityPresentationApi for consistent display Signed-off-by: Julien --- .../MyGroupsPicker/MyGroupsPicker.test.tsx | 22 ++++- .../fields/MyGroupsPicker/MyGroupsPicker.tsx | 83 +++++++++++++------ .../components/fields/VirtualizedListbox.tsx | 33 +++++--- 3 files changed, 100 insertions(+), 38 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx index 94261ce0e4..4e54ee99ad 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx @@ -19,7 +19,10 @@ import { render, waitFor } from '@testing-library/react'; import { CatalogApi } from '@backstage/catalog-client'; import { MyGroupsPicker } from './MyGroupsPicker'; import { TestApiProvider } from '@backstage/test-utils'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { ErrorApi, @@ -29,6 +32,7 @@ import { } from '@backstage/core-plugin-api'; import userEvent from '@testing-library/user-event'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; // Create a mock IdentityApi const mockIdentityApi: IdentityApi = { @@ -117,6 +121,10 @@ describe('', () => { [identityApiRef, mockIdentityApi], [catalogApiRef, catalogApi], [errorApiRef, mockErrorApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], ]} > @@ -189,6 +197,10 @@ describe('', () => { [identityApiRef, mockIdentityApi], [catalogApiRef, catalogApi], [errorApiRef, mockErrorApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], ]} > @@ -246,6 +258,10 @@ describe('', () => { [identityApiRef, mockIdentityApi], [catalogApiRef, catalogApi], [errorApiRef, mockErrorApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], ]} > @@ -306,6 +322,10 @@ describe('', () => { [identityApiRef, mockIdentityApi], [catalogApiRef, catalogApi], [errorApiRef, mockErrorApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], ]} > diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx index 1e78fa45a1..dcb4be4c44 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useState } from 'react'; +import React, { useEffect } from 'react'; import { errorApiRef, identityApiRef, @@ -23,11 +23,19 @@ import { import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; import { MyGroupsPickerProps, MyGroupsPickerSchema } from './schema'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import Autocomplete, { + createFilterOptions, +} from '@material-ui/lab/Autocomplete'; +import { + catalogApiRef, + EntityDisplayName, + entityPresentationApiRef, + EntityRefPresentationSnapshot, +} from '@backstage/plugin-catalog-react'; import { NotFoundError } from '@backstage/errors'; import useAsync from 'react-use/esm/useAsync'; import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { VirtualizedListbox } from '../VirtualizedListbox'; export { MyGroupsPickerSchema }; @@ -43,19 +51,14 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { const identityApi = useApi(identityApiRef); const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); - const [groups, setGroups] = useState< - { - label: string; - ref: string; - }[] - >([]); + const entityPresentationApi = useApi(entityPresentationApiRef); - useAsync(async () => { + const { value: groups, loading } = useAsync(async () => { const { userEntityRef } = await identityApi.getBackstageIdentity(); if (!userEntityRef) { errorApi.post(new NotFoundError('No user entity ref found')); - return; + return { catalogEntities: [], entityRefToPresentation: new Map() }; } const { items } = await catalogApi.getEntities({ @@ -65,24 +68,38 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { }, }); - const groupValues = items - .filter((e): e is Entity => Boolean(e)) - .map(item => ({ - label: item.metadata.title ?? item.metadata.name, - ref: stringifyEntityRef(item), - })); + const entityRefToPresentation = new Map< + string, + EntityRefPresentationSnapshot + >( + await Promise.all( + items.map(async item => { + const presentation = await entityPresentationApi.forEntity(item) + .promise; + return [stringifyEntityRef(item), presentation] as [ + string, + EntityRefPresentationSnapshot, + ]; + }), + ), + ); - setGroups(groupValues); + return { catalogEntities: items, entityRefToPresentation }; }); - const updateChange = ( - _: React.ChangeEvent<{}>, - value: { label: string; ref: string } | null, - ) => { - onChange(value?.ref ?? ''); + const updateChange = (_: React.ChangeEvent<{}>, value: Entity | null) => { + onChange(value ? stringifyEntityRef(value) : ''); }; - const selectedEntity = groups?.find(e => e.ref === formData) || null; + const selectedEntity = + groups?.catalogEntities.find(e => stringifyEntityRef(e) === formData) || + null; + + useEffect(() => { + if (groups?.catalogEntities.length === 1 && !selectedEntity) { + onChange(stringifyEntityRef(groups.catalogEntities[0])); + } + }, [groups, onChange, selectedEntity]); return ( { error={rawErrors?.length > 0} > group.label} + getOptionLabel={option => + groups?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle! + } + autoSelect renderInput={params => ( { FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} variant="outlined" required={required} + InputProps={params.InputProps} /> )} + renderOption={option => } + filterOptions={createFilterOptions({ + stringify: option => + groups?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle!, + })} + ListboxComponent={VirtualizedListbox} /> ); diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx index b0920b626a..b2bd611cec 100644 --- a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx +++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx @@ -22,29 +22,40 @@ const renderRow = (props: ListChildComponentProps) => { return React.cloneElement(data[index], { style }); }; +const OuterElementContext = React.createContext({}); + +const OuterElementType = React.forwardRef((props, ref) => { + const outerProps = React.useContext(OuterElementContext); + return

; +}); + export const VirtualizedListbox = React.forwardRef< HTMLDivElement, { children?: React.ReactNode } >((props, ref) => { - const itemData = React.Children.toArray(props.children); + const { children, ...other } = props; + const itemData = React.Children.toArray(children); const itemCount = itemData.length; const itemSize = 36; const itemsToShow = Math.min(10, itemCount); - const height = Math.max(itemSize, itemsToShow * itemSize - 0.5 * itemSize); + const height = itemsToShow * itemSize; return (
- - {renderRow} - + + + {renderRow} + +
); }); From 1552c334bab3e4ddcc45e77f7a45ed967d84e98e Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 25 Jul 2024 11:51:45 +0200 Subject: [PATCH 032/393] chore: add changeset Signed-off-by: Julien --- .changeset/perfect-cars-jam.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/perfect-cars-jam.md diff --git a/.changeset/perfect-cars-jam.md b/.changeset/perfect-cars-jam.md new file mode 100644 index 0000000000..a22791034a --- /dev/null +++ b/.changeset/perfect-cars-jam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Changed the way to display entities in `MyGroupsPicker` to use entityPresentationApi and make it consistent across scaffolder pickers From 7a05f509084f487972c276e3018589b51b1fea03 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 25 Jul 2024 12:48:06 +0300 Subject: [PATCH 033/393] fix: allow using notifications without users in catalog additionally refactored and added tests for the notification receiver logic fixes #25768 Signed-off-by: Heikki Hellgren --- .changeset/modern-poems-mate.md | 5 + .../src/service/getUsersForEntityRef.test.ts | 175 ++++++++++++++++++ .../src/service/getUsersForEntityRef.ts | 161 ++++++++++++++++ .../src/service/router.ts | 96 +--------- 4 files changed, 343 insertions(+), 94 deletions(-) create mode 100644 .changeset/modern-poems-mate.md create mode 100644 plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts create mode 100644 plugins/notifications-backend/src/service/getUsersForEntityRef.ts diff --git a/.changeset/modern-poems-mate.md b/.changeset/modern-poems-mate.md new file mode 100644 index 0000000000..06926daac9 --- /dev/null +++ b/.changeset/modern-poems-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Allow using notifications without users in the catalog diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts new file mode 100644 index 0000000000..b06d6f203b --- /dev/null +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts @@ -0,0 +1,175 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '@backstage/backend-test-utils'; +import { getUsersForEntityRef } from './getUsersForEntityRef'; +import { CatalogApi } from '@backstage/catalog-client'; +import { + RELATION_HAS_MEMBER, + RELATION_OWNED_BY, + RELATION_PARENT_OF, +} from '@backstage/catalog-model'; + +describe('getUsersForEntityRef', () => { + const catalogApiMock = { + getEntitiesByRefs: jest.fn(), + getEntityByRef: jest.fn(), + }; + const authMock = mockServices.auth(); + + it('should return empty array if entityRef is null', async () => { + await expect( + getUsersForEntityRef(null, [], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual([]); + }); + + it('should resolve users without calling catalog', async () => { + await expect( + getUsersForEntityRef(['user:foo', 'user:ignored'], ['user:ignored'], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual(['user:foo']); + expect(catalogApiMock.getEntitiesByRefs).not.toHaveBeenCalled(); + }); + + it('should resolve group entities to users', async () => { + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'parent_group', + }, + relations: [ + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/foo', + }, + { + type: RELATION_PARENT_OF, + targetRef: 'group:default/child_group', + }, + ], + }, + ], + }); + + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'child_group', + }, + relations: [ + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/bar', + }, + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/ignored', + }, + ], + }, + ], + }); + + await expect( + getUsersForEntityRef( + 'group:default/parent_group', + ['user:default/ignored'], + { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }, + ), + ).resolves.toEqual(['user:default/foo', 'user:default/bar']); + }); + + it('should resolve user owner of entity from entity ref', async () => { + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test_component', + }, + relations: [ + { + type: RELATION_OWNED_BY, + targetRef: 'user:default/foo', + }, + ], + }, + ], + }); + + await expect( + getUsersForEntityRef('component:default/test_component', [], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual(['user:default/foo']); + }); + + it('should resolve group owner of entity from entity ref', async () => { + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test_component', + }, + relations: [ + { + type: RELATION_OWNED_BY, + targetRef: 'group:default/owner_group', + }, + ], + }, + ], + }); + + catalogApiMock.getEntityByRef.mockResolvedValueOnce({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'owner_group', + }, + relations: [ + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/foo', + }, + ], + }); + + await expect( + getUsersForEntityRef('component:default/test_component', [], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual(['user:default/foo']); + }); +}); diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts new file mode 100644 index 0000000000..fd564d6528 --- /dev/null +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts @@ -0,0 +1,161 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Entity, + isGroupEntity, + isUserEntity, + parseEntityRef, + RELATION_HAS_MEMBER, + RELATION_OWNED_BY, + RELATION_PARENT_OF, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogApi } from '@backstage/catalog-client'; + +const isUserEntityRef = (ref: string) => + parseEntityRef(ref).kind.toLocaleLowerCase() === 'user'; + +// Partitions array of entity references to two arrays; user entity refs and other entity refs +const partitionEntityRefs = (refs: string[]): string[][] => + refs.reduce( + ([userEntityRefs, otherEntityRefs]: string[][], ref: string) => { + return isUserEntityRef(ref) + ? [[...userEntityRefs, ref], otherEntityRefs] + : [userEntityRefs, [...otherEntityRefs, ref]]; + }, + [[], []], + ); + +export const getUsersForEntityRef = async ( + entityRef: string | string[] | null, + excludeEntityRefs: string | string[], + options: { + auth: AuthService; + catalogClient: CatalogApi; + }, +): Promise => { + const { auth, catalogClient } = options; + + if (entityRef === null) { + return []; + } + + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); + + const excluded = Array.isArray(excludeEntityRefs) + ? excludeEntityRefs + : [excludeEntityRefs]; + + const refsArr = Array.isArray(entityRef) ? entityRef : [entityRef]; + const [userEntityRefs, otherEntityRefs] = partitionEntityRefs(refsArr); + const users: string[] = userEntityRefs.filter(ref => !excluded.includes(ref)); + const entityRefs = otherEntityRefs.filter(ref => !excluded.includes(ref)); + + const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations']; + let entities: Array = []; + if (entityRefs.length > 0) { + const fetchedEntities = await catalogClient.getEntitiesByRefs( + { + entityRefs, + fields, + }, + { token }, + ); + entities = fetchedEntities.items; + } + + const mapEntity = async (entity: Entity | undefined): Promise => { + if (!entity) { + return []; + } + + const currentEntityRef = stringifyEntityRef(entity); + if (excluded.includes(currentEntityRef)) { + return []; + } + + if (isUserEntity(entity)) { + return [currentEntityRef]; + } + + if (isGroupEntity(entity)) { + if (!entity.relations?.length) { + return []; + } + + const groupUsers = entity.relations + .filter( + relation => + relation.type === RELATION_HAS_MEMBER && + isUserEntityRef(relation.targetRef), + ) + .map(r => r.targetRef); + + const childGroupRefs = entity.relations + .filter(relation => relation.type === RELATION_PARENT_OF) + .map(r => r.targetRef); + + let childGroupUsers: string[][] = []; + if (childGroupRefs.length > 0) { + const childGroups = await catalogClient.getEntitiesByRefs( + { + entityRefs: childGroupRefs, + fields, + }, + { token }, + ); + childGroupUsers = await Promise.all(childGroups.items.map(mapEntity)); + } + + return [...groupUsers, ...childGroupUsers.flat(2)].filter( + ref => !excluded.includes(ref), + ); + } + + if (entity.relations?.length) { + const ownerRef = entity.relations.find( + relation => relation.type === RELATION_OWNED_BY, + )?.targetRef; + + if (!ownerRef) { + return []; + } + + if (isUserEntityRef(ownerRef)) { + if (excluded.includes(ownerRef)) { + return []; + } + return [ownerRef]; + } + + const owner = await catalogClient.getEntityByRef(ownerRef, { token }); + return mapEntity(owner); + } + + return []; + }; + + for (const entity of entities) { + const u = await mapEntity(entity); + users.push(...u); + } + + return [...new Set(users)]; +}; diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 3ca3ce46bc..abd7e2dc13 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -23,15 +23,6 @@ import { } from '../database'; import { v4 as uuid } from 'uuid'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { - Entity, - isGroupEntity, - isUserEntity, - RELATION_HAS_MEMBER, - RELATION_OWNED_BY, - RELATION_PARENT_OF, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { NotificationProcessor, NotificationSendOptions, @@ -54,6 +45,7 @@ import { NotificationStatus, } from '@backstage/plugin-notifications-common'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; +import { getUsersForEntityRef } from './getUsersForEntityRef'; /** @internal */ export interface RouterOptions { @@ -94,91 +86,6 @@ export async function createRouter( return info.userEntityRef; }; - const getUsersForEntityRef = async ( - entityRef: string | string[] | null, - excludeEntityRefs: string | string[], - ): Promise => { - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - if (entityRef === null) { - return []; - } - - const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations']; - - const refs = Array.isArray(entityRef) ? entityRef : [entityRef]; - const entities = await catalogClient.getEntitiesByRefs( - { - entityRefs: refs, - fields, - }, - { token }, - ); - - const excluded = Array.isArray(excludeEntityRefs) - ? excludeEntityRefs - : [excludeEntityRefs]; - - const mapEntity = async (entity: Entity | undefined): Promise => { - if (!entity) { - return []; - } - - const currentEntityRef = stringifyEntityRef(entity); - if (excluded.includes(currentEntityRef)) { - return []; - } - - if (isUserEntity(entity)) { - return [currentEntityRef]; - } else if (isGroupEntity(entity) && entity.relations) { - const users = entity.relations - .filter(relation => relation.type === RELATION_HAS_MEMBER) - .map(r => r.targetRef); - - const childGroupRefs = entity.relations - .filter(relation => relation.type === RELATION_PARENT_OF) - .map(r => r.targetRef); - - const childGroups = await catalogClient.getEntitiesByRefs( - { - entityRefs: childGroupRefs, - fields, - }, - { token }, - ); - const childGroupUsers = await Promise.all( - childGroups.items.map(mapEntity), - ); - return [...users, ...childGroupUsers.flat(2)]; - } else if (entity.relations) { - const ownerRef = entity.relations.find( - relation => relation.type === RELATION_OWNED_BY, - )?.targetRef; - - if (ownerRef) { - const owner = await catalogClient.getEntityByRef(ownerRef, { token }); - if (owner) { - return mapEntity(owner); - } - } - } - - return []; - }; - - const users: string[] = []; - for (const entity of entities.items) { - const u = await mapEntity(entity); - users.push(...u); - } - - return users; - }; - const filterProcessors = (payload: NotificationPayload) => { const result: NotificationProcessor[] = []; @@ -543,6 +450,7 @@ export async function createRouter( users = await getUsersForEntityRef( entityRef, recipients.excludeEntityRef ?? [], + { auth, catalogClient }, ); } catch (e) { logger.error(`Failed to resolve notification receivers: ${e}`); From cd203f12502c08030c44f3b7d02cbbfdce8e0339 Mon Sep 17 00:00:00 2001 From: parmar-abhinav Date: Sat, 27 Jul 2024 15:27:20 +0530 Subject: [PATCH 034/393] Added examples for action github:pages and improved test cases Signed-off-by: parmar-abhinav --- .changeset/thin-carrots-eat.md | 5 + .../githubPagesEnable.examples.test.ts | 364 +++++++++++++++++- .../src/actions/githubPagesEnable.examples.ts | 205 ++++++++++ 3 files changed, 570 insertions(+), 4 deletions(-) create mode 100644 .changeset/thin-carrots-eat.md diff --git a/.changeset/thin-carrots-eat.md b/.changeset/thin-carrots-eat.md new file mode 100644 index 0000000000..570f21f83d --- /dev/null +++ b/.changeset/thin-carrots-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added examples for action github:pages and improved its test cases diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts index ee7b5caa65..f0aa0ea5a2 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts @@ -52,9 +52,14 @@ describe('github:pages', () => { let githubCredentialsProvider: GithubCredentialsProvider; let action: TemplateAction; - const input = yaml.parse(examples[0].example).steps[0].input; const mockContext = createMockActionContext({ - input, + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/', + token: 'gph_YourGitHubToken', + }, }); beforeEach(() => { @@ -68,8 +73,20 @@ describe('github:pages', () => { afterEach(jest.resetAllMocks); - it('should work with example input', async () => { - await action.handler(mockContext); + it(`Should ${examples[0].description}`, async () => { + let input; + try { + input = yaml.parse(examples[0].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); expect(mockOctokit.request).toHaveBeenCalledWith( 'POST /repos/{owner}/{repo}/pages', @@ -87,4 +104,343 @@ describe('github:pages', () => { }, ); }); + it(`Should ${examples[1].description}`, async () => { + let input; + try { + input = yaml.parse(examples[1].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwner', + repo: 'customPathRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/docs', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[2].description}`, async () => { + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'legacyOwner', + repo: 'legacyRepo', + build_type: 'legacy', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[3].description}`, async () => { + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'branchOwner', + repo: 'customBranchRepo', + build_type: 'workflow', + source: { + branch: 'develop', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + it(`Should ${examples[4].description}`, async () => { + let input; + try { + input = yaml.parse(examples[4].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwner', + repo: 'fullCustomRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/docs', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[5].description}`, async () => { + let input; + try { + input = yaml.parse(examples[5].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'minimalOwner', + repo: 'minimalRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + it(`Should ${examples[6].description}`, async () => { + let input; + try { + input = yaml.parse(examples[6].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwner', + repo: 'customBuildPathRepo', + build_type: 'legacy', + source: { + branch: 'main', + path: '/custom-path', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[7].description}`, async () => { + let input; + try { + input = yaml.parse(examples[7].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'branchPathOwner', + repo: 'customBranchPathRepo', + build_type: 'workflow', + source: { + branch: 'feature-branch', + path: '/project-docs', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[8].description}`, async () => { + let input; + try { + input = yaml.parse(examples[8].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwnerName', + repo: 'customRepoName', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[9].description}`, async () => { + let input; + try { + input = yaml.parse(examples[9].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'tokenOwner', + repo: 'customTokenRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/site', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[10].description}`, async () => { + let input; + try { + input = yaml.parse(examples[10].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'tokenOwner', + repo: 'specificTokenRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[11].description}`, async () => { + let input; + try { + input = yaml.parse(examples[11].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'docsOwner', + repo: 'docSiteRepo', + build_type: 'workflow', + source: { + branch: 'docs-branch', + path: '/docs-site', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts index be4a33e5df..57102c4b92 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts @@ -37,4 +37,209 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: + 'Enables GitHub Pages for a repository with a custom source path.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-path', + name: 'Enable GitHub Pages with Custom Source Path', + input: { + repoUrl: 'github.com?repo=customPathRepo&owner=customOwner', + sourcePath: '/docs', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository using legacy build type.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-legacy', + name: 'Enable GitHub Pages with Legacy Build Type', + input: { + repoUrl: 'github.com?repo=legacyRepo&owner=legacyOwner', + buildType: 'legacy', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a custom source branch.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-branch', + name: 'Enable GitHub Pages with Custom Source Branch', + input: { + repoUrl: 'github.com?repo=customBranchRepo&owner=branchOwner', + sourceBranch: 'develop', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + + { + description: + 'Enables GitHub Pages for a repository with full customization.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-full-custom', + name: 'Enable GitHub Pages with Full Customization', + input: { + repoUrl: 'github.com?repo=fullCustomRepo&owner=customOwner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/docs', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with minimal configuration.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-minimal', + name: 'Enable GitHub Pages with Minimal Configuration', + input: { + repoUrl: 'github.com?repo=minimalRepo&owner=minimalOwner', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with custom build type and source path.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-build-path', + name: 'Enable GitHub Pages with Custom Build Type and Source Path', + input: { + repoUrl: 'github.com?repo=customBuildPathRepo&owner=customOwner', + buildType: 'legacy', + sourcePath: '/custom-path', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with custom source branch and path.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-branch-path', + name: 'Enable GitHub Pages with Custom Source Branch and Path', + input: { + repoUrl: + 'github.com?repo=customBranchPathRepo&owner=branchPathOwner', + sourceBranch: 'feature-branch', + sourcePath: '/project-docs', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a custom owner and repository name.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-owner-repo', + name: 'Enable GitHub Pages with Custom Owner and Repository Name', + input: { + repoUrl: 'github.com?repo=customRepoName&owner=customOwnerName', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with full customization and a different token.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-full-custom-diff-token', + name: 'Enable GitHub Pages with Full Customization and Different Token', + input: { + repoUrl: 'github.com?repo=customTokenRepo&owner=tokenOwner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/site', + token: 'gph_DifferentGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a specific token for authorization.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-specific-token', + name: 'Enable GitHub Pages with Specific Token', + input: { + repoUrl: 'github.com?repo=specificTokenRepo&owner=tokenOwner', + token: 'gph_SpecificGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a documentation site with custom configuration.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-doc-site', + name: 'Enable GitHub Pages for Documentation Site', + input: { + repoUrl: 'github.com?repo=docSiteRepo&owner=docsOwner', + buildType: 'workflow', + sourceBranch: 'docs-branch', + sourcePath: '/docs-site', + token: 'gph_DocsGitHubToken', + }, + }, + ], + }), + }, ]; From 9e2918640a05130650f1899f380a546691ebd926 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Sat, 27 Jul 2024 14:28:55 +0100 Subject: [PATCH 035/393] yarn-plugin: implement BackstageResolver#getSatisfying It turns out this method is called for `backstage:` descriptors during `yarn dedupe`, so we need to implement it. Signed-off-by: MT Lewis --- .../src/resolver/BackstageResolver.test.ts | 83 +++++++++++++++++++ .../src/resolver/BackstageResolver.ts | 31 +++++-- 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/packages/yarn-plugin/src/resolver/BackstageResolver.test.ts b/packages/yarn-plugin/src/resolver/BackstageResolver.test.ts index 4cc0c1c9f2..e404970b23 100644 --- a/packages/yarn-plugin/src/resolver/BackstageResolver.test.ts +++ b/packages/yarn-plugin/src/resolver/BackstageResolver.test.ts @@ -178,4 +178,87 @@ describe('BackstageResolver', () => { ).rejects.toThrow(/invalid backstage version/i); }); }); + + describe('getSatisfying', () => { + it('filters out locators for other packages', async () => { + await expect( + backstageResolver.getSatisfying( + structUtils.makeDescriptor( + structUtils.makeIdent('backstage', 'core'), + 'backstage:1.23.45', + ), + {}, + [ + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'foo'), + 'npm:1.2.3', + ), + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'core'), + 'npm:6.7.8', + ), + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'bar'), + 'npm:1.2.3', + ), + ], + ), + ).resolves.toEqual({ + locators: [ + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'core'), + 'npm:6.7.8', + ), + ], + sorted: true, + }); + }); + + it('filters out locators for other package versions', async () => { + await expect( + backstageResolver.getSatisfying( + structUtils.makeDescriptor( + structUtils.makeIdent('backstage', 'core'), + 'backstage:1.23.45', + ), + {}, + [ + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'core'), + 'npm:5.6.7', + ), + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'core'), + 'npm:6.7.8', + ), + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'bar'), + 'npm:7.8.9', + ), + ], + ), + ).resolves.toEqual({ + locators: [ + structUtils.makeLocator( + structUtils.makeIdent('backstage', 'core'), + 'npm:6.7.8', + ), + ], + sorted: true, + }); + }); + + it('throws for non `backstage:` descriptors', async () => { + await expect( + backstageResolver.getSatisfying( + structUtils.makeDescriptor( + structUtils.makeIdent('backstage', 'core'), + 'npm:1.2.3', + ), + {}, + [], + ), + ).rejects.toThrow(/unexpected npm: range/i); + }); + }); }); diff --git a/packages/yarn-plugin/src/resolver/BackstageResolver.ts b/packages/yarn-plugin/src/resolver/BackstageResolver.ts index 33bba5df53..82e6c013a3 100644 --- a/packages/yarn-plugin/src/resolver/BackstageResolver.ts +++ b/packages/yarn-plugin/src/resolver/BackstageResolver.ts @@ -92,6 +92,29 @@ export class BackstageResolver implements Resolver { ]; } + /** + * Given a descriptor and a list of possible locators, return a filtered list + * containing only locators that satisfy the descriptor. Since each Backstage + * release version corresponds to a single version for each package, we just + * need to filter that array for locators with that exact version. + */ + async getSatisfying( + descriptor: Descriptor, + _dependencies: Record, + locators: Array, + ): Promise<{ locators: Locator[]; sorted: boolean }> { + const packageVersion = await getPackageVersion(descriptor); + + return { + locators: locators.filter( + locator => + structUtils.areIdentsEqual(descriptor, locator) && + locator.reference === `npm:${packageVersion}`, + ), + sorted: true, + }; + } + /** * This plugin does not need to support any locators itself, since the * `getCandidates` method will always convert `backstage:` versions into @@ -106,14 +129,6 @@ export class BackstageResolver implements Resolver { */ getResolutionDependencies = () => ({}); - /** - * Candidate versions produced by this resolver always use the `npm:` - * protocol, so this function will never be called. - */ - async getSatisfying(): Promise<{ locators: Locator[]; sorted: boolean }> { - throw new Error('Unreachable'); - } - /** * Once transformed into locators (through getCandidates), the versions are * resolved by the NpmSemverResolver From 496b8a9fd3c8187e4ae0f525a187c60ad0a5b694 Mon Sep 17 00:00:00 2001 From: David Weber Date: Tue, 16 Apr 2024 18:53:13 +0200 Subject: [PATCH 036/393] fix: export `RelatedEntitiesCard` presets Signed-off-by: David Weber Signed-off-by: David Weber --- .changeset/angry-mice-juggle.md | 5 ++++ .../RelatedEntitiesCard.tsx | 27 ++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .changeset/angry-mice-juggle.md diff --git a/.changeset/angry-mice-juggle.md b/.changeset/angry-mice-juggle.md new file mode 100644 index 0000000000..c5c90b7365 --- /dev/null +++ b/.changeset/angry-mice-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Export `RelatedEntitiesCard` presets to be reused. diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx index dd1359482d..e6e375646d 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx +++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx @@ -31,6 +31,17 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + asComponentEntities, + asResourceEntities, + asSystemEntities, + componentEntityColumns, + componentEntityHelpLink, + resourceEntityColumns, + resourceEntityHelpLink, + systemEntityColumns, + systemEntityHelpLink, +} from './presets'; import { catalogTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -59,9 +70,9 @@ export type RelatedEntitiesCardProps = { * * @public */ -export function RelatedEntitiesCard( +export const RelatedEntitiesCard = ( props: RelatedEntitiesCardProps, -) { +) => { const { variant = 'gridItem', title, @@ -116,4 +127,14 @@ export function RelatedEntitiesCard( tableOptions={tableOptions} /> ); -} +}; + +RelatedEntitiesCard.componentEntityColumns = componentEntityColumns; +RelatedEntitiesCard.componentEntityHelpLink = componentEntityHelpLink; +RelatedEntitiesCard.asComponentEntities = asComponentEntities; +RelatedEntitiesCard.resourceEntityColumns = resourceEntityColumns; +RelatedEntitiesCard.resourceEntityHelpLink = resourceEntityHelpLink; +RelatedEntitiesCard.asResourceEntities = asResourceEntities; +RelatedEntitiesCard.systemEntityColumns = systemEntityColumns; +RelatedEntitiesCard.systemEntityHelpLink = systemEntityHelpLink; +RelatedEntitiesCard.asSystemEntities = asSystemEntities; From 658279973ecc56b36df752c3afebde698c04e485 Mon Sep 17 00:00:00 2001 From: David Weber Date: Mon, 8 Jul 2024 17:35:59 +0200 Subject: [PATCH 037/393] feat: provide table options everywhere and add title to api tables Signed-off-by: David Weber Signed-off-by: David Weber --- .changeset/weak-jobs-joke.md | 6 ++++++ plugins/api-docs/api-report.md | 13 ++++++++++++ .../components/ApisCards/ConsumedApisCard.tsx | 17 ++++++++++++---- .../src/components/ApisCards/HasApisCard.tsx | 17 ++++++++++++---- .../components/ApisCards/ProvidedApisCard.tsx | 17 ++++++++++++---- plugins/catalog/api-report.md | 19 ++++++++++++++++++ .../DependencyOfComponentsCard.tsx | 18 ++++++++++++++--- .../HasComponentsCard/HasComponentsCard.tsx | 20 +++++++++++++++---- .../HasResourcesCard/HasResourcesCard.tsx | 20 +++++++++++++++---- .../HasSubcomponentsCard.tsx | 16 ++++++++++----- .../HasSystemsCard/HasSystemsCard.tsx | 20 +++++++++++++++---- 11 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 .changeset/weak-jobs-joke.md diff --git a/.changeset/weak-jobs-joke.md b/.changeset/weak-jobs-joke.md new file mode 100644 index 0000000000..8127605f07 --- /dev/null +++ b/.changeset/weak-jobs-joke.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +--- + +Add `tableOptions` to all tables and additionally `title` to API tables. diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 2fc852777b..0fd35bdaa0 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -16,6 +16,7 @@ import { JSX as JSX_2 } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TableColumn } from '@backstage/core-components'; +import { TableOptions } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; @@ -88,7 +89,9 @@ export type AsyncApiDefinitionWidgetProps = { // @public (undocumented) export const ConsumedApisCard: (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => React_2.JSX.Element; // @public (undocumented) @@ -118,7 +121,9 @@ export const EntityApiDefinitionCard: () => JSX_2.Element; // @public (undocumented) export const EntityConsumedApisCard: (props: { variant?: InfoCardVariants | undefined; + title?: string | undefined; columns?: TableColumn[] | undefined; + tableOptions?: TableOptions<{}> | undefined; }) => JSX_2.Element; // @public (undocumented) @@ -129,13 +134,17 @@ export const EntityConsumingComponentsCard: (props: { // @public (undocumented) export const EntityHasApisCard: (props: { variant?: InfoCardVariants | undefined; + title?: string | undefined; columns?: TableColumn[] | undefined; + tableOptions?: TableOptions<{}> | undefined; }) => JSX_2.Element; // @public (undocumented) export const EntityProvidedApisCard: (props: { variant?: InfoCardVariants | undefined; + title?: string | undefined; columns?: TableColumn[] | undefined; + tableOptions?: TableOptions<{}> | undefined; }) => JSX_2.Element; // @public (undocumented) @@ -156,7 +165,9 @@ export type GraphQlDefinitionWidgetProps = { // @public (undocumented) export const HasApisCard: (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => React_2.JSX.Element; // @public (undocumented) @@ -185,7 +196,9 @@ export type PlainApiDefinitionWidgetProps = { // @public (undocumented) export const ProvidedApisCard: (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => React_2.JSX.Element; // @public (undocumented) diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index d5494d1818..f68e6dcb40 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -30,6 +30,7 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; @@ -38,9 +39,16 @@ import { */ export const ConsumedApisCard = (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => { - const { variant = 'gridItem', columns = apiEntityColumns } = props; + const { + variant = 'gridItem', + title = 'Consumed APIs', + columns = apiEntityColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_CONSUMES_API, @@ -48,7 +56,7 @@ export const ConsumedApisCard = (props: { if (loading) { return ( - + ); @@ -56,7 +64,7 @@ export const ConsumedApisCard = (props: { if (error || !entities) { return ( - + @@ -84,6 +92,7 @@ export const ConsumedApisCard = (props: {
} columns={columns} + tableOptions={tableOptions} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index d58a654176..6810d9d2ba 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -30,6 +30,7 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; @@ -46,9 +47,16 @@ const presetColumns: TableColumn[] = [ */ export const HasApisCard = (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => { - const { variant = 'gridItem', columns = presetColumns } = props; + const { + variant = 'gridItem', + title = 'APIs', + columns = presetColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_HAS_PART, @@ -57,7 +65,7 @@ export const HasApisCard = (props: { if (loading) { return ( - + ); @@ -65,7 +73,7 @@ export const HasApisCard = (props: { if (error || !entities) { return ( - + @@ -93,6 +101,7 @@ export const HasApisCard = (props: {
} columns={columns} + tableOptions={tableOptions} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 97d0a00b0a..48099df375 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -30,6 +30,7 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; @@ -38,9 +39,16 @@ import { */ export const ProvidedApisCard = (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => { - const { variant = 'gridItem', columns = apiEntityColumns } = props; + const { + variant = 'gridItem', + title = 'Provided APIs', + columns = apiEntityColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_PROVIDES_API, @@ -48,7 +56,7 @@ export const ProvidedApisCard = (props: { if (loading) { return ( - + ); @@ -56,7 +64,7 @@ export const ProvidedApisCard = (props: { if (error || !entities) { return ( - + @@ -84,6 +92,7 @@ export const ProvidedApisCard = (props: { } columns={columns} + tableOptions={tableOptions} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index c1fb52fb94..e705126b7d 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -33,6 +33,7 @@ import { SearchResultListItemExtensionProps } from '@backstage/plugin-search-rea import { StarredEntitiesApi } from '@backstage/plugin-catalog-react'; import { StorageApi } from '@backstage/core-plugin-api'; import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { SystemEntity } from '@backstage/catalog-model'; import { TableColumn } from '@backstage/core-components'; import { TableOptions } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; @@ -304,6 +305,10 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi { // @public (undocumented) export interface DependencyOfComponentsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -516,6 +521,10 @@ export function hasCatalogProcessingErrors( // @public (undocumented) export interface HasComponentsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -535,6 +544,10 @@ export function hasRelationWarnings( // @public (undocumented) export interface HasResourcesCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -543,6 +556,8 @@ export interface HasResourcesCardProps { // @public (undocumented) export interface HasSubcomponentsCardProps { + // (undocumented) + columns?: TableColumn[]; // (undocumented) tableOptions?: TableOptions; // (undocumented) @@ -553,6 +568,10 @@ export interface HasSubcomponentsCardProps { // @public (undocumented) export interface HasSystemsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx index 617d009d4f..2f0557d11e 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx @@ -14,8 +14,15 @@ * limitations under the License. */ -import { RELATION_DEPENDENCY_OF } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { + ComponentEntity, + RELATION_DEPENDENCY_OF, +} from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asComponentEntities, @@ -30,6 +37,8 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export interface DependencyOfComponentsCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function DependencyOfComponentsCard( @@ -39,6 +48,8 @@ export function DependencyOfComponentsCard( const { variant = 'gridItem', title = t('dependencyOfComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, } = props; return ( ); } diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index 917543dd12..13a9e75928 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asComponentEntities, @@ -30,21 +34,29 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export interface HasComponentsCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasComponentsCard(props: HasComponentsCardProps) { const { t } = useTranslationRef(catalogTranslationRef); - const { variant = 'gridItem', title = t('hasComponentsCard.title') } = props; + const { + variant = 'gridItem', + title = t('hasComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, + } = props; return ( ); } diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index d11f10c882..b99bca1716 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { RELATION_HAS_PART, ResourceEntity } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asResourceEntities, @@ -30,21 +34,29 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export interface HasResourcesCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasResourcesCard(props: HasResourcesCardProps) { const { t } = useTranslationRef(catalogTranslationRef); - const { variant = 'gridItem', title = t('hasResourcesCard.title') } = props; + const { + variant = 'gridItem', + title = t('hasResourcesCard.title'), + columns = resourceEntityColumns, + tableOptions = {}, + } = props; return ( ); } diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index d81349794a..ceea839ef4 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants, TableOptions } from '@backstage/core-components'; +import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asComponentEntities, @@ -28,16 +32,18 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSubcomponentsCardProps { variant?: InfoCardVariants; - tableOptions?: TableOptions; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasSubcomponentsCard(props: HasSubcomponentsCardProps) { const { t } = useTranslationRef(catalogTranslationRef); const { variant = 'gridItem', - tableOptions = {}, title = t('hasSubcomponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, } = props; return ( []; + tableOptions?: TableOptions; } export function HasSystemsCard(props: HasSystemsCardProps) { const { t } = useTranslationRef(catalogTranslationRef); - const { variant = 'gridItem', title = t('hasSystemsCard.title') } = props; + const { + variant = 'gridItem', + title = t('hasSystemsCard.title'), + columns = systemEntityColumns, + tableOptions = {}, + } = props; return ( ); } From a7504f40673723625daf4ed38b818ea5be36a3c2 Mon Sep 17 00:00:00 2001 From: David Weber Date: Mon, 29 Jul 2024 02:20:29 +0200 Subject: [PATCH 038/393] chore: add simple icons plugin Signed-off-by: David Weber Signed-off-by: David Weber --- microsite/data/plugins/simple-icons.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 microsite/data/plugins/simple-icons.yaml diff --git a/microsite/data/plugins/simple-icons.yaml b/microsite/data/plugins/simple-icons.yaml new file mode 100644 index 0000000000..45722ab421 --- /dev/null +++ b/microsite/data/plugins/simple-icons.yaml @@ -0,0 +1,10 @@ +--- +title: Simple Icons +author: dweber019 +authorUrl: https://github.com/dweber019 +category: Visualization +description: The Simple Icons plugin will add additional icons to be used as link icons. +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/simple-icons +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/simple-icons/docs/pluginIcon.png +npmPackageName: '@dweber019/backstage-plugin-simple-icons' +addedDate: '2024-07-30' From b76e877bcf2de9ffcea62d5b49183587ce6b0aed Mon Sep 17 00:00:00 2001 From: Evan Kelly Date: Tue, 18 Jun 2024 14:40:41 +0100 Subject: [PATCH 039/393] added script for datadog script to docs Signed-off-by: Evan Kelly --- docs/integrations/datadog-rum/installation.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 4dc6c24134..b5f2a494c9 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -36,3 +36,48 @@ There are two optional arguments: - `site`: The Datadog site of your organization; defaults to `datadoghq.com` - `env`: The application environment for Datadog events (no default) + +## Script to fix Datadog RUM not Publishing Issue + +Copy and paste this section to your `packages/app/public/index.html` + +``` +<% if (config.has('app.datadogRum')) { %> + + <% } %> +``` From 09d7e3f1d9d813655b286c280bb80044946d870a Mon Sep 17 00:00:00 2001 From: Evan Kelly Date: Wed, 26 Jun 2024 14:14:09 +0100 Subject: [PATCH 040/393] run prettier check Signed-off-by: Evan Kelly --- docs/integrations/datadog-rum/installation.md | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index b5f2a494c9..1301f111f7 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -42,42 +42,42 @@ There are two optional arguments: Copy and paste this section to your `packages/app/public/index.html` ``` -<% if (config.has('app.datadogRum')) { %> - - <% } %> +<% if (config.has('app.datadogRum')) { %> + + <% } %> ``` From 51bf5fa47d87b8ca76c5fa9469b1362c065bb8d0 Mon Sep 17 00:00:00 2001 From: Evan Kelly Date: Tue, 2 Jul 2024 10:53:39 +0100 Subject: [PATCH 041/393] make change suggested in pr Signed-off-by: Evan Kelly --- docs/integrations/datadog-rum/installation.md | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 1301f111f7..f636b100a1 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -26,20 +26,7 @@ app: # sessionReplaySampleRate: 0 ``` -If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. - -The `clientToken` and `applicationId` are generated from the Datadog RUM page -following -[these instructions](https://docs.datadoghq.com/real_user_monitoring/browser/). - -There are two optional arguments: - -- `site`: The Datadog site of your organization; defaults to `datadoghq.com` -- `env`: The application environment for Datadog events (no default) - -## Script to fix Datadog RUM not Publishing Issue - -Copy and paste this section to your `packages/app/public/index.html` +If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. If you a Datadog not publishing issue copy and paste this section in to your `packages/app/public/index.html` ``` <% if (config.has('app.datadogRum')) { %> @@ -81,3 +68,12 @@ Copy and paste this section to your `packages/app/public/index.html` <% } %> ``` + +The `clientToken` and `applicationId` are generated from the Datadog RUM page +following +[these instructions](https://docs.datadoghq.com/real_user_monitoring/browser/). + +There are two optional arguments: + +- `site`: The Datadog site of your organization; defaults to `datadoghq.com` +- `env`: The application environment for Datadog events (no default) From 100ecdcb947e18d015ce26312da55c48b9196f53 Mon Sep 17 00:00:00 2001 From: Evan Kelly Date: Wed, 10 Jul 2024 08:07:38 +0100 Subject: [PATCH 042/393] changes for comments on pr Signed-off-by: Evan Kelly --- docs/integrations/datadog-rum/installation.md | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index f636b100a1..5afd05d6cb 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -26,47 +26,49 @@ app: # sessionReplaySampleRate: 0 ``` -If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. If you a Datadog not publishing issue copy and paste this section in to your `packages/app/public/index.html` +If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. -``` +:::note +In case after a proper configuration, the events still arent being capture copy and paste this section in to your `packages/app/public/index.html` under the `` tag. + +```html <% if (config.has('app.datadogRum')) { %> - - <% } %> + +<% } %> ``` The `clientToken` and `applicationId` are generated from the Datadog RUM page From 3f88be264f8fe1027a438d96772fdbb987b84b54 Mon Sep 17 00:00:00 2001 From: "Kelly, Evan" Date: Fri, 12 Jul 2024 09:31:01 +0100 Subject: [PATCH 043/393] Update installation.md Signed-off-by: Kelly, Evan Signed-off-by: Evan Kelly --- docs/integrations/datadog-rum/installation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 5afd05d6cb..414317d593 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -28,6 +28,8 @@ app: If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. +Please note, there's a bug where env value MUST be specified at build time + :::note In case after a proper configuration, the events still arent being capture copy and paste this section in to your `packages/app/public/index.html` under the `` tag. From 6d4cb97f071c17ccd285d83c6a34e938ec77c745 Mon Sep 17 00:00:00 2001 From: parmar-abhinav Date: Tue, 30 Jul 2024 12:41:44 +0530 Subject: [PATCH 044/393] Added examples for github:repo:create action and improved test cases Signed-off-by: parmar-abhinav --- .changeset/itchy-experts-tie.md | 5 + .../actions/githubRepoCreate.examples.test.ts | 2215 +++++++++++++++++ .../src/actions/githubRepoCreate.examples.ts | 928 +++++++ 3 files changed, 3148 insertions(+) create mode 100644 .changeset/itchy-experts-tie.md diff --git a/.changeset/itchy-experts-tie.md b/.changeset/itchy-experts-tie.md new file mode 100644 index 0000000000..c5bf99ecaf --- /dev/null +++ b/.changeset/itchy-experts-tie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added examples for github:repo:create action and improved test cases diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index 843a32f758..1a81a54c64 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -58,6 +58,7 @@ const mockOctokit = { getRepoPublicKey: jest.fn(), }, }, + request: jest.fn().mockResolvedValue({}), }; jest.mock('octokit', () => ({ Octokit: class { @@ -188,4 +189,2218 @@ describe('github:repo:create examples', () => { has_wiki: false, // disable wiki }); }); + + it(`Should ${examples[3].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://example.com', + }); + }); + + it(`Should ${examples[4].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[4].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[5].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[5].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[6].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[6].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[7].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[7].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: false, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[8].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[8].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'pull_request_title', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[9].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[9].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'blank', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[10].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[10].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: true, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[11].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[11].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[12].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[12].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[13].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[13].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[14].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[14].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[15].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[15].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[16].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[16].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[17].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[17].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: true, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[18].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[18].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[19].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[19].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[20].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[20].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: undefined, + description: 'A repository for project XYZ', + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://project-xyz.com', + }); + }); + + it(`Should ${examples[21].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[21].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[22].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[22].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[23].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[23].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: true, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[24].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[24].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[25].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[25].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: false, + description: undefined, + has_projects: false, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[26].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[26].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[27].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[27].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[28].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[28].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: 'Repository for project ABC', + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[29].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[29].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'public', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[30].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[30].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[31].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[31].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[32].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[32].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[33].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[33].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[34].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[34].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: undefined, + description: 'Internal repository for team collaboration', + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://internal.example.com', + }); + }); + + it(`Should ${examples[35].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[35].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[36].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[36].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: true, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[37].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[37].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[38].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[38].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[39].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[39].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[40].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[40].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: 'Repository for web application project', + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://webapp.example.com', + }); + }); + + it(`Should ${examples[41].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[41].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'pull_request_description', + allow_merge_commit: false, + allow_rebase_merge: false, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[42].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[42].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: false, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[43].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[43].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[44].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[44].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[45].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[45].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'public', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[46].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[46].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: false, + description: 'Repository for backend service', + has_projects: undefined, + has_wiki: false, + homepage: undefined, + }); + }); + + it(`Should ${examples[47].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[47].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[48].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[48].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[49].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[49].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[50].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[50].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: true, + allow_auto_merge: true, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[51].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[51].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://example.com', + }); + }); + + it(`Should ${examples[52].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[52].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: 'Repository for microservice development', + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[53].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[53].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[54].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[54].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: true, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[55].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[55].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[56].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[56].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts index a58da1ee29..1c1f1ca5d2 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts @@ -63,4 +63,932 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Set repository homepage.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with homepage', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + homepage: 'https://example.com', + }, + }, + ], + }), + }, + { + description: 'Create a private repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + }, + }, + ], + }), + }, + { + description: 'Enable required code owner reviews.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required code owner reviews', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireCodeOwnerReviews: true, + }, + }, + ], + }), + }, + { + description: 'Set required approving review count to 2.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required approving review count', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, + { + description: 'Allow squash merge only.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only squash merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowSquashMerge: true, + allowRebaseMerge: false, + }, + }, + ], + }), + }, + { + description: 'Set squash merge commit title to pull request title.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with squash merge commit title set to pull request title', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + squashMergeCommitTitle: 'pull_request_title', + }, + }, + ], + }), + }, + { + description: 'Set squash merge commit message to blank.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with squash merge commit message set to blank', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + squashMergeCommitMessage: 'blank', + }, + }, + ], + }), + }, + { + description: 'Allow auto-merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing auto-merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowAutoMerge: true, + }, + }, + ], + }), + }, + { + description: 'Set collaborators with push access.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with collaborators having push access', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + collaborators: [ + { username: 'user1', permission: 'push' }, + { username: 'user2', permission: 'push' }, + ], + }, + }, + ], + }), + }, + { + description: 'Add topics to repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + topics: ['devops', 'kubernetes', 'ci-cd'], + }, + }, + ], + }), + }, + { + description: 'Add secret variables to repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with secret variables', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + secrets: [ + { name: 'SECRET_KEY', value: 'supersecretkey' }, + { name: 'API_TOKEN', value: 'tokenvalue' }, + ], + }, + }, + ], + }), + }, + { + description: 'Enable branch protection requiring status checks.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection requiring status checks', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: ['ci/circleci: build'], + }, + }, + ], + }), + }, + { + description: 'Require branches to be up-to-date before merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring branches to be up-to-date before merging', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireBranchesToBeUpToDate: true, + }, + }, + ], + }), + }, + { + description: 'Require conversation resolution before merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring conversation resolution before merging', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredConversationResolution: true, + }, + }, + ], + }), + }, + { + description: 'Delete branch on merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch deletion on merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + deleteBranchOnMerge: true, + }, + }, + ], + }), + }, + { + description: 'Customize OIDC token.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC token customization', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: 'https://github.com', + }, + }, + }, + ], + }), + }, + { + description: 'Require commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring commit signing', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Set multiple properties including description, homepage, and visibility.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with multiple properties', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'A repository for project XYZ', + homepage: 'https://project-xyz.com', + repoVisibility: 'internal', + }, + }, + ], + }), + }, + { + description: 'Configure branch protection with multiple settings.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection settings', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: [ + 'ci/circleci: build', + 'ci/circleci: test', + ], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: true, + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, + { + description: + 'Set repository access to private and add collaborators with admin access.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository with collaborators', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + collaborators: [ + { username: 'admin1', permission: 'admin' }, + { username: 'admin2', permission: 'admin' }, + ], + }, + }, + ], + }), + }, + { + description: 'Enable GitHub Projects for the repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with GitHub Projects enabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + hasProjects: true, + }, + }, + ], + }), + }, + { + description: + 'Disable merge commits and allow only rebase and squash merges.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only rebase and squash merges', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowRebaseMerge: true, + allowSquashMerge: true, + }, + }, + ], + }), + }, + { + description: + 'Set repository access to internal with no projects and issues.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new internal GitHub repository without projects and issues', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'internal', + hasProjects: false, + hasIssues: false, + }, + }, + ], + }), + }, + { + description: + 'Create repository with OIDC customization for specific audience.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC customization for specific audience', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: 'https://specific-audience.com', + }, + }, + }, + ], + }), + }, + { + description: 'Require all branches to be up-to-date before merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring all branches to be up-to-date', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireBranchesToBeUpToDate: true, + }, + }, + ], + }), + }, + { + description: 'Set description and topics for the repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with description and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Repository for project ABC', + topics: ['python', 'machine-learning', 'data-science'], + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to public and enable commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new public GitHub repository with commit signing required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'public', + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with collaborators and default branch protection.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with collaborators and branch protection', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + collaborators: [ + { username: 'contributor1', permission: 'write' }, + { username: 'contributor2', permission: 'write' }, + ], + requiredStatusCheckContexts: ['ci/travis: build'], + }, + }, + ], + }), + }, + { + description: 'Add multiple secret variables.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with multiple secret variables', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + secrets: [ + { name: 'SECRET_KEY_1', value: 'value1' }, + { name: 'SECRET_KEY_2', value: 'value2' }, + ], + }, + }, + ], + }), + }, + { + description: 'Require a minimum of 2 approving reviews for merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with 2 required approving reviews', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, + { + description: + 'Enable branch protection with conversation resolution required.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection and conversation resolution required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredConversationResolution: true, + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to internal with description and homepage.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new internal GitHub repository with description and homepage', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'internal', + description: 'Internal repository for team collaboration', + homepage: 'https://internal.example.com', + }, + }, + ], + }), + }, + { + description: 'Disable auto-merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with auto-merge disabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowAutoMerge: false, + }, + }, + ], + }), + }, + { + description: 'Set repository topics and enable GitHub Projects.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with topics and GitHub Projects enabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + topics: ['opensource', 'nodejs', 'api'], + hasProjects: true, + }, + }, + ], + }), + }, + { + description: + 'Create a private repository with collaborators having admin and write access.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository with multiple collaborators', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + collaborators: [ + { username: 'admin1', permission: 'admin' }, + { username: 'writer1', permission: 'write' }, + ], + }, + }, + ], + }), + }, + { + description: 'Disable branch deletion on merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch deletion on merge disabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + deleteBranchOnMerge: false, + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to internal and enable commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new internal GitHub repository with commit signing required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'internal', + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create repository with description, homepage, and required status checks.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with description, homepage, and status checks', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Repository for web application project', + homepage: 'https://webapp.example.com', + requiredStatusCheckContexts: [ + 'ci/travis: build', + 'ci/travis: lint', + ], + }, + }, + ], + }), + }, + { + description: + 'Enable squash merges only and set commit message to pull request description.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only squash merges with commit message set to pull request description', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowSquashMerge: true, + allowRebaseMerge: false, + squashMergeCommitMessage: 'pull_request_description', + }, + }, + ], + }), + }, + { + description: 'Enable rebase merges only and require commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only rebase merges with commit signing required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowRebaseMerge: true, + allowSquashMerge: false, + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create repository with OIDC customization for multiple audiences.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC customization for multiple audiences', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: ['https://audience1.com', 'https://audience2.com'], + }, + }, + }, + ], + }), + }, + { + description: + 'Enable branch protection with required approving reviews and status checks.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection requiring approving reviews and status checks', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredApprovingReviewCount: 2, + requiredStatusCheckContexts: [ + 'ci/circleci: build', + 'ci/circleci: test', + ], + }, + }, + ], + }), + }, + { + description: 'Create a public repository with topics and secret variables.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new public GitHub repository with topics and secret variables', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'public', + topics: ['javascript', 'react', 'frontend'], + secrets: [ + { name: 'API_KEY', value: 'apikeyvalue' }, + { name: 'DB_PASSWORD', value: 'dbpasswordvalue' }, + ], + }, + }, + ], + }), + }, + { + description: 'Set repository description and disable issues and wiki.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with description, and disable issues and wiki', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Repository for backend service', + hasIssues: false, + hasWiki: false, + }, + }, + ], + }), + }, + { + description: 'Enable required conversation resolution and commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required conversation resolution and commit signing', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredConversationResolution: true, + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to private and require branches to be up-to-date.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository requiring branches to be up-to-date', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + requireBranchesToBeUpToDate: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with default settings and add multiple topics.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with default settings and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + topics: ['devops', 'ci-cd', 'automation'], + }, + }, + ], + }), + }, + { + description: + 'Disable merge commits, enable auto-merge, and require commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository disabling merge commits, enabling auto-merge, and requiring commit signing', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowAutoMerge: true, + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with homepage, collaborators, and topics.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with homepage, collaborators, and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + homepage: 'https://example.com', + collaborators: [ + { username: 'user1', permission: 'push' }, + { username: 'user2', permission: 'admin' }, + ], + topics: ['opensource', 'contribution'], + }, + }, + ], + }), + }, + { + description: 'Create a repository with branch protection and description.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection and description', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: ['ci/travis: build'], + requiredApprovingReviewCount: 1, + description: 'Repository for microservice development', + }, + }, + ], + }), + }, + { + description: 'Create a repository with OIDC customization and topics.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC customization and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: 'https://api.example.com', + }, + topics: ['api', 'security'], + }, + }, + ], + }), + }, + { + description: + 'Enable required code owner reviews and branch deletion on merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required code owner reviews and branch deletion on merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireCodeOwnerReviews: true, + deleteBranchOnMerge: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with multiple secret variables and collaborators.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with multiple secret variables and collaborators', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + secrets: [ + { name: 'API_SECRET', value: 'secretvalue' }, + { name: 'DB_USER', value: 'dbuser' }, + ], + collaborators: [ + { username: 'dev1', permission: 'write' }, + { username: 'dev2', permission: 'push' }, + ], + }, + }, + ], + }), + }, + { + description: + 'Enable branch protection requiring status checks and conversation resolution.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection requiring status checks and conversation resolution', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: ['ci/build'], + requiredConversationResolution: true, + }, + }, + ], + }), + }, ]; From a65ccea1ffdc13387b9ac54ad2a54ad36d11924c Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Tue, 30 Jul 2024 09:35:44 +0200 Subject: [PATCH 045/393] Update flat-papayas-push.md Signed-off-by: Ben Lambert --- .changeset/flat-papayas-push.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/flat-papayas-push.md b/.changeset/flat-papayas-push.md index 191dad8550..f9e20613fd 100644 --- a/.changeset/flat-papayas-push.md +++ b/.changeset/flat-papayas-push.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend-module-azure': minor +'@backstage/plugin-scaffolder-backend-module-azure': patch --- Added examples for publish:azure action and updated its test cases From 4fdb1f4eee2ffd5d2835ae216d02068e4c80f9fb Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Tue, 30 Jul 2024 06:59:50 -0500 Subject: [PATCH 046/393] Conditionally Render Sign In Provider Example Signed-off-by: Andre Wanlin --- docs/auth/index.md | 60 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/auth/index.md b/docs/auth/index.md index 5188d7e2a4..de579d039a 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -112,9 +112,16 @@ const app = createApp({ }); ``` -You can also use the `providers` prop to enable multiple sign-in methods, for example +:::note Note -- allowing guest access: +You can configure sign-in to use a redirect flow with no pop-up by adding +`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml` + +::: + +### Using Multiple Providers + +You can also use the `providers` prop to enable multiple sign-in methods, for example to allow guest access: ```tsx title="packages/app/src/App.tsx" const app = createApp({ @@ -140,12 +147,53 @@ const app = createApp({ }); ``` -:::note Note +### Conditionally Render Sign In Provider -You can configure sign-in to use a redirect flow with no pop-up by adding -`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml` +In the above example you have both Guest and GitHub sign-in options, this is helpful for non-production but in Production you will most likely not want to offer Guest access. You can easily use information from your config to help conditionally render the provider: -::: +```tsx title="packages/app/src/App.tsx" +import { + configApiRef, + githubAuthApiRef, + useApi, +} from '@backstage/core-plugin-api'; + +const app = createApp({ + components: { + SignInPage: props => { + const configApi = useApi(configApiRef); + if (configApi.getString('auth.environment') === 'development') { + return ( + + ); + } + return ( + + ); + }, + }, + // .. +}); +``` ## Sign-In with Proxy Providers From a16632cf740a6180a62183d1544b57142ae88ec0 Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Wed, 31 Jul 2024 00:01:45 +0200 Subject: [PATCH 047/393] fix(techdocs-backend): update config.d.ts Signed-off-by: Thomas Cardonne --- .changeset/shaggy-mugs-return.md | 5 +++++ plugins/techdocs-backend/config.d.ts | 32 +++++++++++++--------------- 2 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 .changeset/shaggy-mugs-return.md diff --git a/.changeset/shaggy-mugs-return.md b/.changeset/shaggy-mugs-return.md new file mode 100644 index 0000000000..6af5d1d18f --- /dev/null +++ b/.changeset/shaggy-mugs-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Update configuration schema to match actual behavior diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 9d1cd9e170..e22fc549d9 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -68,22 +68,14 @@ export interface Config { /** * Techdocs publisher information */ - publisher?: - | { - type: - | 'local' - | 'googleGcs' - | 'awsS3' - | 'azureBlobStorage' - | 'openStackSwift'; - - local?: { - /** - * Directory to store generated static files. - */ - publishDirectory?: string; - }; - } + publisher?: { + local?: { + /** + * Directory to store generated static files. + */ + publishDirectory?: string; + }; + } & ( | { type: 'awsS3'; @@ -132,6 +124,11 @@ export interface Config { * (Required) Cloud Storage Bucket Name */ bucketName: string; + /** + * (Optional) Location in storage bucket to save files + * If not set, the default location will be the root of the storage bucket + */ + bucketRootPath?: string; /** * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. @@ -261,7 +258,8 @@ export interface Config { */ projectId?: string; }; - }; + } + ); /** * @example http://localhost:7007/api/techdocs From 83faf24b3367b5daf84d47c374c7dcae85ab5071 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 31 Jul 2024 07:46:33 +0300 Subject: [PATCH 048/393] feat: add possibility to allow and deny specific email addresses Notification email processor config to support allowing and denying specific email addresses who can receive notifications in email. This is required to be able to test this in test environments with real production data and not send unnecessary notifications. Signed-off-by: Heikki Hellgren --- .changeset/witty-queens-run.md | 5 ++++ .../config.d.ts | 8 ++++++ .../processor/NotificationsEmailProcessor.ts | 27 +++++++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .changeset/witty-queens-run.md diff --git a/.changeset/witty-queens-run.md b/.changeset/witty-queens-run.md new file mode 100644 index 0000000000..2d869caa91 --- /dev/null +++ b/.changeset/witty-queens-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +--- + +Notification email processor supports allowing or denying specific email addresses from receiving notifications diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 92f3385d1b..e4ce5ede53 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -132,6 +132,14 @@ export interface Config { */ excludedTopics?: string[]; }; + /** + * White list of addresses to send email to + */ + allowlistEmailAddresses?: string[]; + /** + * Black list of addresses to not send email to + */ + denylistEmailAddresses?: string[]; }; }; }; diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 3bfccfcc90..f3d9f59b8c 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -56,6 +56,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly throttleInterval: number; private readonly frontendBaseUrl: string; private readonly filter: NotificationProcessorFilters; + private readonly allowlistEmailAddresses?: string[]; + private readonly denylistEmailAddresses?: string[]; constructor( private readonly logger: LoggerService, @@ -85,6 +87,12 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) : 3_600_000; this.frontendBaseUrl = config.getString('app.baseUrl'); + this.allowlistEmailAddresses = emailProcessorConfig.getOptionalStringArray( + 'allowlistEmailAddresses', + ); + this.denylistEmailAddresses = emailProcessorConfig.getOptionalStringArray( + 'denylistEmailAddresses', + ); this.filter = getProcessorFiltersFromConfig(emailProcessorConfig); } @@ -197,10 +205,25 @@ export class NotificationsEmailProcessor implements NotificationProcessor { notification: Notification, options: NotificationSendOptions, ) { + let emails: string[]; if (options.recipients.type === 'broadcast' || notification.user === null) { - return await this.getBroadcastEmails(); + emails = await this.getBroadcastEmails(); + } else { + emails = await this.getUserEmail(notification.user); } - return await this.getUserEmail(notification.user); + + if (this.allowlistEmailAddresses) { + emails = emails.filter(email => + this.allowlistEmailAddresses?.includes(email), + ); + } + + if (this.denylistEmailAddresses) { + emails = emails.filter( + email => !this.denylistEmailAddresses?.includes(email), + ); + } + return emails; } private async sendMail(options: Mail.Options) { From ff6a0a381892dddab9dbfe28fdac83c36202e084 Mon Sep 17 00:00:00 2001 From: Valber Junior <84424883+ValberJunior@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:25:48 -0300 Subject: [PATCH 049/393] Create kubernetes-gpt-analyzer.yaml Signed-off-by: Valber Junior <84424883+ValberJunior@users.noreply.github.com> --- .../data/plugins/kubernetes-gpt-analyzer.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 microsite/data/plugins/kubernetes-gpt-analyzer.yaml diff --git a/microsite/data/plugins/kubernetes-gpt-analyzer.yaml b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml new file mode 100644 index 0000000000..78bd0fe4c4 --- /dev/null +++ b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml @@ -0,0 +1,16 @@ +--- +title: VeeCode Kubernetes GPT Analyzer +author: VeeCode Platform +authorUrl: https://platform.vee.codes/ +category: Monitoring +description: The Kubernetes GPT Analyzer plug-in uses artificial intelligence with the help of k8s-operator to analyze and optimize your Kubernetes entities, improving the management and performance of your cluster. It makes it easier to detect anomalies and suggest best practices. +documentation: https://platform.vee.codes/plugin/Kubernetes%20GPT%20Analyzer/ +iconUrl: https://veecode-platform.github.io/support/imgs/logo_4.svg +npmPackageName: '@veecode-platform/backstage-plugin-kubernetes-gpt-analyzer' +tags: + - monitor + - ai + - kubernetes + - k8soperator + - gpt +addedDate: '2024-07-31' From 8897f3c442db12366ce28efb328c56fe51af5295 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Thu, 1 Aug 2024 07:16:55 -0500 Subject: [PATCH 050/393] Changes based on feedback Signed-off-by: Andre Wanlin --- docs/features/search/collators.md | 3 +-- microsite/sidebars.json | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/features/search/collators.md b/docs/features/search/collators.md index 9d68f71c32..4054ec1212 100644 --- a/docs/features/search/collators.md +++ b/docs/features/search/collators.md @@ -4,8 +4,7 @@ title: Collators description: Indexing you Backstage content with Collators --- -You need to be able to search something! Collators are the way to define what -can be searched. Backstage includes 2 collators out of the box for the [Catalog](#catalog) and [TechDocs](#techdocs). There's also some from the [Backstage Community](#community-collators) too! +Backstage includes 2 [collators](./concepts.md#collators) out of the box for the [Catalog](#catalog) and [TechDocs](#techdocs). There's also some from the [Backstage Community](#community-collators) too! ## Catalog diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 2d533912f3..782b39e810 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -132,8 +132,8 @@ "features/search/concepts", "features/search/architecture", "features/search/search-engines", - "features/search/how-to-guides", - "features/search/collators" + "features/search/collators", + "features/search/how-to-guides" ] }, { From ea4ed1e0a03494786daa52bd539f8282078ec783 Mon Sep 17 00:00:00 2001 From: Rogerio Angeliski Date: Thu, 1 Aug 2024 11:28:40 -0300 Subject: [PATCH 051/393] add catalog-backend-module-gcp plugin Signed-off-by: Rogerio Angeliski --- .../data/plugins/catalog-backend-module-gcp.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 microsite/data/plugins/catalog-backend-module-gcp.yaml diff --git a/microsite/data/plugins/catalog-backend-module-gcp.yaml b/microsite/data/plugins/catalog-backend-module-gcp.yaml new file mode 100644 index 0000000000..b5df858862 --- /dev/null +++ b/microsite/data/plugins/catalog-backend-module-gcp.yaml @@ -0,0 +1,16 @@ +--- +title: GCP Entity Providers +author: BackToStage +authorUrl: https://backtostage.app/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=catalog-backend-module-gcp +category: Catalog +description: Import your Infrastructure from GCP into Backstage as Resource Entities +documentation: https://github.com/backtostage/backstage-plugins/blob/main/plugins/catalog-backend-module-gcp/README.md +iconUrl: https://avatars1.githubusercontent.com/u/2810941?s=280&v=4 +npmPackageName: '@backtostage/plugin-catalog-backend-module-gcp' +tags: + - cloud + - project + - resources + - gcp + - cloudsql +addedDate: '2024-08-01' From 405916c10a5f38ed3bc332dcbcde35812422c486 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Thu, 1 Aug 2024 14:54:17 -0500 Subject: [PATCH 052/393] apply requested changes Signed-off-by: Paul Schultz --- beps/00010-event-auditor/README.md | 69 +++++++----------------------- 1 file changed, 15 insertions(+), 54 deletions(-) diff --git a/beps/00010-event-auditor/README.md b/beps/00010-event-auditor/README.md index 5f4e767806..b1af69cf95 100644 --- a/beps/00010-event-auditor/README.md +++ b/beps/00010-event-auditor/README.md @@ -75,7 +75,7 @@ This interface defines the information related to the actor who triggered the lo ```ts export type ActorDetails = { - actorId: string | null; + actorId?: string; ip?: string; hostname?: string; userAgent?: string; @@ -88,16 +88,12 @@ These interfaces define the structure of request and response data that might be ```ts export type AuditRequest = { - body: any; url: string; method: string; - params?: any; - query?: any; }; export type AuditResponse = { status: number; - body?: any; }; ``` @@ -114,20 +110,12 @@ export type AuditEventSuccessStatus = { status: 'succeeded' }; /** * Indicates the event failed and includes details about the encountered errors. */ -export type AuditEventFailureStatus = { +export type AuditEventFailureStatus = { status: 'failed'; - errors: ErrorLike[]; + errors: E; }; -/** - * Indicates the event failed with errors that don't perfectly fit the expected structure. - */ -export type AuditEventUnknownFailureStatus = { - status: 'failed'; - errors: unknown[]; -}; - -export type AuditEventStatus = AuditEventSuccessStatus | AuditEventFailureStatus; +export type AuditEventStatus = AuditEventSuccessStatus | AuditEventFailureStatus | undefined; ``` #### EventAuditor Interface @@ -139,45 +127,28 @@ This interface defines the functionalities of an `EventAuditor` class. This clas ```ts /** - * Common fields of an audit event. Note: timestamp and pluginId are automatically added at event creation. + * Common fields of an audit event. * * @public */ -export type AuditEventDetails = { - actor: ActorDetails; - eventName: string; - stage: string; - request?: AuditRequest; - response?: AuditResponse; - meta: JsonValue; - isAuditEvent: true; -} & AuditEventStatus; - -export type AuditEventDetailsOptions = { - eventName: string; - stage: string; - metadata?: JsonValue; - response?: AuditResponse; - actorId?: string; - request?: Request; -} & (AuditEventSuccessStatus | AuditEventUnknownFailureStatus); - -export type AuditEventOptions = { +export type AuditEventOptions = AuditEventStatus & { eventName: string; message: string; stage: string; level?: 'info' | 'debug' | 'warn' | 'error'; - actorId?: string; metadata?: JsonValue; response?: AuditResponse; request?: Request; -} & (AuditEventSuccessStatus | AuditEventUnknownFailureStatus); +} & ({ actorId: string; } | { credentials: BackstageCredentials } | undefined); -export type EventAuditorOptions = { - logger: LoggerService; - authService: AuthService; - httpAuthService: HttpAuthService; -}; +export type AuditEvent = { + actor: ActorDetails; + eventName: string; + stage: string; + isAuditLog: true; + request?: AuditRequest; + response?: AuditResponse; +} & AuditLogStatus; export interface EventAuditor { /** @@ -187,16 +158,6 @@ export interface EventAuditor { */ getActorId(request?: Request): Promise; - /** - * Generates the audit event details to place in the metadata argument of the logger - * - * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object - * @public - */ - createAuditEventDetails( - options: AuditEventDetailsOptions, - ): Promise; - /** * Generates an Audit Event and logs it at the level passed by the user. * Supports `info`, `debug`, `warn` or `error` level. Defaults to `info` if no level is passed. From 716c9fbaa9e75f74e96412b5bb09c1bc5b21a180 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Thu, 1 Aug 2024 15:41:59 -0500 Subject: [PATCH 053/393] Update beps/00010-event-auditor/README.md Co-authored-by: Frank Kong <50030060+Zaperex@users.noreply.github.com> Signed-off-by: Paul Schultz --- beps/00010-event-auditor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beps/00010-event-auditor/README.md b/beps/00010-event-auditor/README.md index b1af69cf95..96cafb701e 100644 --- a/beps/00010-event-auditor/README.md +++ b/beps/00010-event-auditor/README.md @@ -112,7 +112,7 @@ export type AuditEventSuccessStatus = { status: 'succeeded' }; */ export type AuditEventFailureStatus = { status: 'failed'; - errors: E; + errors: E[]; }; export type AuditEventStatus = AuditEventSuccessStatus | AuditEventFailureStatus | undefined; From 5cedd9f828e8b408502ea5d4644c3de06ceaf6ba Mon Sep 17 00:00:00 2001 From: Kamil Markow Date: Mon, 5 Aug 2024 13:19:31 -0400 Subject: [PATCH 054/393] Fix TechDocs Edit URL link when trailing slash not present Signed-off-by: Kamil Markow --- .changeset/tricky-rules-destroy.md | 5 +++++ plugins/techdocs-node/src/stages/generate/helpers.test.ts | 1 + plugins/techdocs-node/src/stages/generate/helpers.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/tricky-rules-destroy.md diff --git a/.changeset/tricky-rules-destroy.md b/.changeset/tricky-rules-destroy.md new file mode 100644 index 0000000000..a709e5b429 --- /dev/null +++ b/.changeset/tricky-rules-destroy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Fix TechDocs Edit URL for nexted docs diff --git a/plugins/techdocs-node/src/stages/generate/helpers.test.ts b/plugins/techdocs-node/src/stages/generate/helpers.test.ts index 6ee3870845..695f5b8f2c 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.test.ts @@ -115,6 +115,7 @@ describe('helpers', () => { url | repo_url | edit_uri ${'https://github.com/backstage/backstage'} | ${'https://github.com/backstage/backstage'} | ${undefined} ${'https://github.com/backstage/backstage/tree/main/examples/techdocs/'} | ${'https://github.com/backstage/backstage/tree/main/examples/techdocs/'} | ${'https://github.com/backstage/backstage/edit/main/examples/techdocs/docs'} + ${'https://github.com/backstage/backstage/tree/main/examples/techdocs'} | ${'https://github.com/backstage/backstage/tree/main/examples/techdocs'} | ${'https://github.com/backstage/backstage/edit/main/examples/techdocs/docs'} ${'https://github.com/backstage/backstage/tree/main/'} | ${'https://github.com/backstage/backstage/tree/main/'} | ${'https://github.com/backstage/backstage/edit/main/docs'} ${'https://gitlab.com/backstage/backstage'} | ${'https://gitlab.com/backstage/backstage'} | ${undefined} ${'https://gitlab.com/backstage/backstage/-/blob/main/examples/techdocs/'} | ${'https://gitlab.com/backstage/backstage/-/blob/main/examples/techdocs/'} | ${'https://gitlab.com/backstage/backstage/-/edit/main/examples/techdocs/docs'} diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index 5efe96877f..9c1996a772 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -117,7 +117,7 @@ export const getRepoUrlFromLocationAnnotation = ( const sourceFolder = integration.resolveUrl({ url: `./${docsFolder}`, - base: target, + base: target.endsWith('/') ? target : `${target}/`, }); return { repo_url: target, From 18916691d7c998989f2e9e69d5a3beba5be74f21 Mon Sep 17 00:00:00 2001 From: Kamil Markow Date: Mon, 5 Aug 2024 13:33:57 -0400 Subject: [PATCH 055/393] Fix typo Signed-off-by: Kamil Markow --- .changeset/tricky-rules-destroy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tricky-rules-destroy.md b/.changeset/tricky-rules-destroy.md index a709e5b429..196702f8c4 100644 --- a/.changeset/tricky-rules-destroy.md +++ b/.changeset/tricky-rules-destroy.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs-node': patch --- -Fix TechDocs Edit URL for nexted docs +Fix TechDocs Edit URL for nested docs From 8f1500e118a75ae40c0fec1802d19a787dcf5fd5 Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Thu, 4 Jul 2024 11:16:40 +0200 Subject: [PATCH 056/393] fix: use parameterized query Signed-off-by: Chris Langhout --- .../src/entrypoints/database/connectors/postgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 444bbc0d18..7d9d943fc7 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -52,7 +52,7 @@ export function createPgDatabaseClient( database.client.pool.on( 'createSuccess', async (_event: number, pgClient: Client) => { - await pgClient.query(`SET ROLE ${role}`); + await pgClient.query('SET ROLE $1', [role]); }, ); } From 81f930aeed19074adb914c59486b2519e52ec091 Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Thu, 4 Jul 2024 11:51:58 +0200 Subject: [PATCH 057/393] chore: add changeset Signed-off-by: Chris Langhout --- .changeset/sixty-kiwis-poke.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sixty-kiwis-poke.md diff --git a/.changeset/sixty-kiwis-poke.md b/.changeset/sixty-kiwis-poke.md new file mode 100644 index 0000000000..d4380cbd88 --- /dev/null +++ b/.changeset/sixty-kiwis-poke.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +use parameterized query to prevent chance of SQL-injection From ed83fcc3e7b94d0d955a093ed48a173ad4c9588a Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Tue, 6 Aug 2024 15:11:28 +0200 Subject: [PATCH 058/393] fix: use pg-format to allow dynamic identifier Signed-off-by: Chris Langhout --- .changeset/sixty-kiwis-poke.md | 2 +- packages/backend-defaults/package.json | 1 + .../src/entrypoints/database/connectors/postgres.ts | 4 +++- yarn.lock | 8 ++++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.changeset/sixty-kiwis-poke.md b/.changeset/sixty-kiwis-poke.md index d4380cbd88..5281c6ade0 100644 --- a/.changeset/sixty-kiwis-poke.md +++ b/.changeset/sixty-kiwis-poke.md @@ -2,4 +2,4 @@ '@backstage/backend-defaults': patch --- -use parameterized query to prevent chance of SQL-injection +use formatted query to prevent chance of SQL-injection diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 196c9b188a..3341c96c75 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -171,6 +171,7 @@ "path-to-regexp": "^6.2.1", "pg": "^8.11.3", "pg-connection-string": "^2.3.0", + "pg-format": "^1.0.4", "raw-body": "^2.4.1", "selfsigned": "^2.0.0", "stoppable": "^1.1.0", diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 7d9d943fc7..afaa946c05 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -29,6 +29,7 @@ import { Connector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; +import { format } from 'pg-format'; // Limits the number of concurrent DDL operations to 1 const ddlLimiter = limiterFactory(1); @@ -52,7 +53,8 @@ export function createPgDatabaseClient( database.client.pool.on( 'createSuccess', async (_event: number, pgClient: Client) => { - await pgClient.query('SET ROLE $1', [role]); + const query = format('SET ROLE %I', role); + await pgClient.query(query); }, ); } diff --git a/yarn.lock b/yarn.lock index c14251269c..75552e383b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3683,6 +3683,7 @@ __metadata: path-to-regexp: ^6.2.1 pg: ^8.11.3 pg-connection-string: ^2.3.0 + pg-format: ^1.0.4 raw-body: ^2.4.1 selfsigned: ^2.0.0 stoppable: ^1.1.0 @@ -36955,6 +36956,13 @@ __metadata: languageName: node linkType: hard +"pg-format@npm:^1.0.4": + version: 1.0.4 + resolution: "pg-format@npm:1.0.4" + checksum: 159b43ad57d2f963f1072def86080dd2a6dd42c1a86046e388d47b491e00afe795139520eb01c8dffc43ac0243c77b3c4c5882d0ec5f488bb3281f17458b1b3d + languageName: node + linkType: hard + "pg-int8@npm:1.0.1": version: 1.0.1 resolution: "pg-int8@npm:1.0.1" From fed70efe37f27ce7f589108475c3be29aa4d69d0 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 5 Aug 2024 16:03:35 +0200 Subject: [PATCH 059/393] chore: working through the inputs merging Signed-off-by: blam --- .../wiring/createExtensionBlueprint.test.tsx | 66 +++++++++++++++++++ .../src/wiring/createExtensionBlueprint.ts | 39 +++++++---- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index dfdbfface6..5f1324fe7c 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -384,4 +384,70 @@ describe('createExtensionBlueprint', () => { expect(true).toBe(true); }); + + it('should allow merging of inputs', () => { + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + inputs: { + test: createExtensionInput([coreExtensionData.routeRef], { + singleton: true, + }), + }, + output: [coreExtensionData.reactElement.optional()], + factory(_params: { x?: string }, { inputs }) { + const ref: RouteRef = inputs.test.get(coreExtensionData.routeRef); + + unused(ref); + return []; + }, + }); + + blueprint.make({ + inputs: { + test2: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + }, + factory(origFactory, { inputs }) { + const ref: RouteRef = inputs.test.get(coreExtensionData.routeRef); + + const el: JSX.Element = inputs.test2.get( + coreExtensionData.reactElement, + ); + + unused(ref, el); + + return origFactory({}); + }, + }); + + expect(true).toBe(true); + }); + + it('should not allow overriding inputs', () => { + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + inputs: { + test: createExtensionInput([coreExtensionData.routeRef]), + }, + output: [coreExtensionData.reactElement.optional()], + factory() { + return []; + }, + }); + + blueprint.make({ + inputs: { + // @ts-expect-error + test: createExtensionInput([]), // Overrides are not allowed + }, + factory(origFactory) { + return origFactory({}); + }, + }); + + expect(true).toBe(true); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 46db278bf8..90736703a0 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -83,7 +83,6 @@ export interface ExtensionBlueprint< { optional: boolean; singleton: boolean } >; }, - UExtraOutput extends AnyExtensionDataRef, TConfig extends { [key in string]: unknown }, TConfigInput extends { [key in string]: unknown }, TDataRefs extends { [name in string]: AnyExtensionDataRef }, @@ -101,13 +100,23 @@ export interface ExtensionBlueprint< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, + UExtraOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, >( args: { namespace?: string; name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; - inputs?: TInputs; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; output?: Array; config?: { schema: TExtensionConfigSchema & { @@ -132,7 +141,7 @@ export interface ExtensionBlueprint< ReturnType >; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable; } & VerifyExtensionFactoryOutput< @@ -172,7 +181,6 @@ class ExtensionBlueprintImpl< { optional: boolean; singleton: boolean } >; }, - UExtraOutput extends AnyExtensionDataRef, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, TDataRefs extends { [name in string]: AnyExtensionDataRef }, > { @@ -196,12 +204,22 @@ class ExtensionBlueprintImpl< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, + UExtraOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, >(args: { namespace?: string; name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; - inputs?: TInputs; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; output?: Array; params?: TParams; config?: { @@ -228,7 +246,7 @@ class ExtensionBlueprintImpl< } & { [key in keyof TConfigSchema]: z.infer>; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable; }): ExtensionDefinition< @@ -277,7 +295,7 @@ class ExtensionBlueprintImpl< name: args.name ?? name, attachTo: args.attachTo ?? this.options.attachTo, disabled: args.disabled ?? this.options.disabled, - inputs: args.inputs ?? this.options.inputs, + inputs: { ...args.inputs, ...this.options.inputs }, output: [...(args.output ?? []), ...this.options.output], config: Object.keys(schema).length === 0 ? undefined : { schema }, factory: ({ node, config, inputs }) => { @@ -297,7 +315,7 @@ class ExtensionBlueprintImpl< return this.options.factory(innerParams, { node, config: innerContext?.config ?? config, - inputs: innerContext?.inputs ?? inputs, + inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden }); }, { @@ -317,7 +335,7 @@ class ExtensionBlueprintImpl< }, } as CreateExtensionOptions< UOutput, - TInputs, + TInputs & TExtraInputs, { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType @@ -357,7 +375,6 @@ export function createExtensionBlueprint< { optional: boolean; singleton: boolean } >; }, - UExtraOutput extends AnyExtensionDataRef, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, @@ -374,7 +391,6 @@ export function createExtensionBlueprint< TParams, UOutput, TInputs, - UExtraOutput, string extends keyof TConfigSchema ? {} : { [key in keyof TConfigSchema]: z.infer> }, @@ -391,7 +407,6 @@ export function createExtensionBlueprint< TParams, UOutput, TInputs, - UExtraOutput, string extends keyof TConfigSchema ? {} : { From 65a1f99d0320179a22129956d5f6291f1765d73a Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 5 Aug 2024 17:11:56 +0200 Subject: [PATCH 060/393] chore: Fixing some typescript errors Signed-off-by: blam --- .../src/wiring/createExtensionBlueprint.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 90736703a0..465bb10ee6 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -328,7 +328,10 @@ class ExtensionBlueprintImpl< return this.options.factory(args.params, { node, config, - inputs, + // TODO: Figure out types once legacy data map input type is gone + inputs: inputs as unknown as Expand< + ResolvedExtensionInputs + >, }); } throw new Error('Either params or factory must be provided'); From 34f1b2aeb1234428fe74e4581161845dd8fbfdff Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 5 Aug 2024 17:13:50 +0200 Subject: [PATCH 061/393] chore: changeset Signed-off-by: blam --- .changeset/six-mails-smell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/six-mails-smell.md diff --git a/.changeset/six-mails-smell.md b/.changeset/six-mails-smell.md new file mode 100644 index 0000000000..db39259632 --- /dev/null +++ b/.changeset/six-mails-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Support merging of `inputs` in `Blueprints` From 1533b959ef56550d5dfa38ecd4591845796522b1 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 5 Aug 2024 17:35:13 +0200 Subject: [PATCH 062/393] chore: steps to making outputs and factories look pretty Signed-off-by: blam --- .../src/wiring/createExtension.ts | 36 +++++---- .../wiring/createExtensionBlueprint.test.tsx | 76 +++++++++++++++++++ .../src/wiring/createExtensionBlueprint.ts | 6 +- 3 files changed, 102 insertions(+), 16 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index a8bf4ef17c..101c5cd43a 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -148,7 +148,9 @@ export interface LegacyCreateExtensionOptions< /** @ignore */ export type VerifyExtensionFactoryOutput< UDeclaredOutput extends AnyExtensionDataRef, - UFactoryOutput extends ExtensionDataValue, + UFactoryOutput extends + | ExtensionDataValue + | ExtensionDataContainer, > = ( UDeclaredOutput extends any ? UDeclaredOutput['config']['optional'] extends true @@ -156,21 +158,29 @@ export type VerifyExtensionFactoryOutput< : UDeclaredOutput['id'] : never ) extends infer IRequiredOutputIds - ? [IRequiredOutputIds] extends [UFactoryOutput['id']] - ? [UFactoryOutput['id']] extends [UDeclaredOutput['id']] - ? {} + ? ( + UFactoryOutput extends ExtensionDataValue + ? UFactoryOutput['id'] + : UFactoryOutput extends ExtensionDataContainer + ? IDataRefs['id'] + : never + ) extends infer IFactoryOutputIds + ? [IRequiredOutputIds] extends [IFactoryOutputIds] + ? [IFactoryOutputIds] extends [UDeclaredOutput['id']] + ? {} + : { + 'Error: The extension factory has undeclared output(s)': Exclude< + IFactoryOutputIds, + UDeclaredOutput['id'] + >; + } : { - 'Error: The extension factory has undeclared output(s)': Exclude< - UFactoryOutput['id'], - UDeclaredOutput['id'] + 'Error: The extension factory is missing the following output(s)': Exclude< + IRequiredOutputIds, + IFactoryOutputIds >; } - : { - 'Error: The extension factory is missing the following output(s)': Exclude< - IRequiredOutputIds, - UFactoryOutput['id'] - >; - } + : never : never; /** @public */ diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 5f1324fe7c..fed8185df1 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -21,6 +21,7 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { createExtensionDataRef } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; +import { toInternalExtensionDefinition } from './createExtension'; function unused(..._any: any[]) {} @@ -450,4 +451,79 @@ describe('createExtensionBlueprint', () => { expect(true).toBe(true); }); + + it('should replace the outputs when provided through make', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1], + factory() { + return [testDataRef1('foo')]; + }, + }); + + const ext = toInternalExtensionDefinition( + blueprint.make({ + output: [testDataRef2], + factory(origFactory) { + const parent = origFactory({}); + return [testDataRef2(`${parent.get(testDataRef1)}bar`)]; + }, + }), + ); + + expect(ext.output).toEqual([testDataRef2]); + }); + + it('should allow returning of the parent data container', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1], + factory() { + return [testDataRef1('foo')]; + }, + }); + + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield origFactory({}); + yield testDataRef2('bar'); + }, + }); + + expect(true).toBe(true); + // todo: test that the data is actually available + }); + + it('should not allow returning parent output if outputs are overridden', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1.optional()], + factory() { + return [testDataRef1('foo')]; + }, + }); + + blueprint.make({ + output: [testDataRef2.optional()], + *factory(origFactory) { + // yield testDataRef1('bar'); + yield testDataRef2('bar'); + }, + }); + + expect(true).toBe(true); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 465bb10ee6..e56c30410d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -204,7 +204,7 @@ class ExtensionBlueprintImpl< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, - UExtraOutput extends AnyExtensionDataRef, + UNewOutput extends AnyExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput< AnyExtensionDataRef, @@ -220,7 +220,7 @@ class ExtensionBlueprintImpl< [KName in keyof TInputs]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; - output?: Array; + output?: Array; params?: TParams; config?: { schema: TExtensionConfigSchema; @@ -296,7 +296,7 @@ class ExtensionBlueprintImpl< attachTo: args.attachTo ?? this.options.attachTo, disabled: args.disabled ?? this.options.disabled, inputs: { ...args.inputs, ...this.options.inputs }, - output: [...(args.output ?? []), ...this.options.output], + output: args.output ?? this.options.output, config: Object.keys(schema).length === 0 ? undefined : { schema }, factory: ({ node, config, inputs }) => { if (args.factory) { From eda2165093264814f648d0e9db7f495149701a79 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Aug 2024 15:47:57 +0200 Subject: [PATCH 063/393] frontend-plugin-api: working types for extension data containers as iterables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.ts | 12 +++++++++++- .../src/wiring/createExtensionBlueprint.test.tsx | 12 +++++++++--- .../src/wiring/createExtensionBlueprint.ts | 14 +++++++------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 101c5cd43a..2c82c44ee1 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -65,7 +65,17 @@ export type ExtensionDataValues = { /** @public */ export type ExtensionDataContainer = - { + Iterable< + UExtensionData extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? IConfig['optional'] extends true + ? never + : ExtensionDataValue + : never + > & { get( ref: ExtensionDataRef, ): UExtensionData extends ExtensionDataRef diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index fed8185df1..70d874d9ad 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -494,11 +494,18 @@ describe('createExtensionBlueprint', () => { blueprint.make({ output: [testDataRef1, testDataRef2], *factory(origFactory) { - yield origFactory({}); + yield* origFactory({}); yield testDataRef2('bar'); }, }); + blueprint.make({ + output: [testDataRef1, testDataRef2], + factory(origFactory) { + return [...origFactory({}), testDataRef2('bar')]; + }, + }); + expect(true).toBe(true); // todo: test that the data is actually available }); @@ -518,8 +525,7 @@ describe('createExtensionBlueprint', () => { blueprint.make({ output: [testDataRef2.optional()], - *factory(origFactory) { - // yield testDataRef1('bar'); + *factory() { yield testDataRef2('bar'); }, }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index e56c30410d..603ffda621 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -18,6 +18,7 @@ import { AppNode } from '../apis'; import { Expand } from '../types'; import { CreateExtensionOptions, + ExtensionDataContainer, ExtensionDefinition, ResolvedExtensionInputs, VerifyExtensionFactoryOutput, @@ -27,7 +28,6 @@ import { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; import { AnyExtensionDataRef, - ExtensionDataRefToValue, ExtensionDataValue, } from './createExtensionDataRef'; @@ -100,7 +100,7 @@ export interface ExtensionBlueprint< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, - UExtraOutput extends AnyExtensionDataRef, + UNewOutput extends AnyExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput< AnyExtensionDataRef, @@ -117,7 +117,7 @@ export interface ExtensionBlueprint< [KName in keyof TInputs]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; - output?: Array; + output?: Array; config?: { schema: TExtensionConfigSchema & { [KName in keyof TConfig]?: `Error: Config key '${KName & @@ -133,7 +133,7 @@ export interface ExtensionBlueprint< config?: TConfig; inputs?: Expand>; }, - ) => Iterable>, + ) => ExtensionDataContainer, context: { node: AppNode; config: TConfig & { @@ -145,7 +145,7 @@ export interface ExtensionBlueprint< }, ): Iterable; } & VerifyExtensionFactoryOutput< - UOutput & UExtraOutput, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, UFactoryOutput >) | { @@ -236,7 +236,7 @@ class ExtensionBlueprintImpl< }; inputs?: Expand>; }, - ) => Iterable>, + ) => ExtensionDataContainer, context: { node: AppNode; config: { @@ -311,7 +311,7 @@ class ExtensionBlueprintImpl< }; inputs?: Expand>; }, - ): Iterable> => { + ): ExtensionDataContainer => { return this.options.factory(innerParams, { node, config: innerContext?.config ?? config, From e8867e17f1fb751cb47b85d273fb93a2ba4e3bc3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Aug 2024 17:23:26 +0200 Subject: [PATCH 064/393] frontend-plugin-api: extension data container runtime + tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- .../wiring/createExtensionBlueprint.test.tsx | 99 +++++++++++++++---- .../src/wiring/createExtensionBlueprint.ts | 37 ++++++- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 70d874d9ad..66c3939a96 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -21,10 +21,21 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { createExtensionDataRef } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; -import { toInternalExtensionDefinition } from './createExtension'; +import { + ExtensionDefinition, + toInternalExtensionDefinition, +} from './createExtension'; function unused(..._any: any[]) {} +function factoryOutput(ext: ExtensionDefinition) { + const int = toInternalExtensionDefinition(ext); + if (int.version !== 'v2') { + throw new Error('Expected v2 extension'); + } + return Array.from(int.factory({} as any)); +} + describe('createExtensionBlueprint', () => { it('should allow creation of extension blueprints', () => { const TestExtensionBlueprint = createExtensionBlueprint({ @@ -476,6 +487,8 @@ describe('createExtensionBlueprint', () => { ); expect(ext.output).toEqual([testDataRef2]); + + expect(factoryOutput(ext)).toEqual([testDataRef2('foobar')]); }); it('should allow returning of the parent data container', () => { @@ -491,23 +504,28 @@ describe('createExtensionBlueprint', () => { }, }); - blueprint.make({ - output: [testDataRef1, testDataRef2], - *factory(origFactory) { - yield* origFactory({}); - yield testDataRef2('bar'); - }, - }); + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield* origFactory({}); + yield testDataRef2('bar'); + }, + }), + ), + ).toEqual([testDataRef1('foo'), testDataRef2('bar')]); - blueprint.make({ - output: [testDataRef1, testDataRef2], - factory(origFactory) { - return [...origFactory({}), testDataRef2('bar')]; - }, - }); - - expect(true).toBe(true); - // todo: test that the data is actually available + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef1, testDataRef2], + factory(origFactory) { + return [...origFactory({}), testDataRef2('bar')]; + }, + }), + ), + ).toEqual([testDataRef1('foo'), testDataRef2('bar')]); }); it('should not allow returning parent output if outputs are overridden', () => { @@ -523,13 +541,58 @@ describe('createExtensionBlueprint', () => { }, }); + // @ts-expect-error blueprint.make({ output: [testDataRef2.optional()], *factory() { + yield testDataRef1('foo'); yield testDataRef2('bar'); }, }); - expect(true).toBe(true); + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef2.optional()], + *factory() { + yield testDataRef2('bar'); + }, + }), + ), + ).toEqual([testDataRef2('bar')]); + }); + + it('should not rely on optional outputs when forwarding from parent', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1, testDataRef2.optional()], + factory() { + return [testDataRef1('foo')]; + }, + }); + + // @ts-expect-error + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield* origFactory({}); + }, + }); + + expect( + factoryOutput( + blueprint.make({ + output: [testDataRef1, testDataRef2], + *factory(origFactory) { + yield* origFactory({}); + yield testDataRef2('bar'); + }, + }), + ), + ).toEqual([testDataRef1('foo'), testDataRef2('bar')]); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 603ffda621..2b80dac81e 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -28,6 +28,7 @@ import { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; import { AnyExtensionDataRef, + ExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; @@ -169,6 +170,30 @@ export interface ExtensionBlueprint< >; } +/** @internal */ +function createDataContainer( + values: Iterable< + UData extends ExtensionDataRef + ? ExtensionDataValue + : never + >, +): ExtensionDataContainer { + const container = new Map>(); + + for (const output of values) { + container.set(output.id, output); + } + + return { + get(ref) { + return container.get(ref.id)?.value; + }, + [Symbol.iterator]() { + return container.values(); + }, + } as ExtensionDataContainer; +} + /** * @internal */ @@ -312,11 +337,13 @@ class ExtensionBlueprintImpl< inputs?: Expand>; }, ): ExtensionDataContainer => { - return this.options.factory(innerParams, { - node, - config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden - }); + return createDataContainer( + this.options.factory(innerParams, { + node, + config: innerContext?.config ?? config, + inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + }), + ); }, { node, From 8015a2d81c730c28be30598cc1cb9cd5a9fa5dcc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Aug 2024 17:27:18 +0200 Subject: [PATCH 065/393] changesets: update blueprints changeset Signed-off-by: Patrik Oldsberg --- .changeset/six-mails-smell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/six-mails-smell.md b/.changeset/six-mails-smell.md index db39259632..3845c72626 100644 --- a/.changeset/six-mails-smell.md +++ b/.changeset/six-mails-smell.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': patch --- -Support merging of `inputs` in `Blueprints` +Support merging of `inputs` in extension blueprints, but stop merging `output`. In addition, the original factory in extension blueprints now returns a data container that both provides access to the returned data, but can also be forwarded as output. From 51a4ef638b0406c7bbb1ade7a267ae33af3d91b9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Aug 2024 17:29:14 +0200 Subject: [PATCH 066/393] frontend-plugin-api: update API report Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/api-report.md | 39 ++++++++++++++++------ 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index cbe92463ba..38b980fcbf 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -603,7 +603,6 @@ export function createExtensionBlueprint< } >; }, - UExtraOutput extends AnyExtensionDataRef, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, @@ -624,7 +623,6 @@ export function createExtensionBlueprint< TParams, UOutput, TInputs, - UExtraOutput, string extends keyof TConfigSchema ? {} : { @@ -1090,7 +1088,6 @@ export interface ExtensionBlueprint< } >; }, - UExtraOutput extends AnyExtensionDataRef, TConfig extends { [key in string]: unknown; }, @@ -1108,6 +1105,16 @@ export interface ExtensionBlueprint< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, >( args: { namespace?: string; @@ -1117,8 +1124,11 @@ export interface ExtensionBlueprint< input: string; }; disabled?: boolean; - inputs?: TInputs; - output?: Array; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; config?: { schema: TExtensionConfigSchema & { [KName in keyof TConfig]?: `Error: Config key '${KName & @@ -1134,7 +1144,7 @@ export interface ExtensionBlueprint< config?: TConfig; inputs?: Expand>; }, - ) => Iterable>, + ) => ExtensionDataContainer, context: { node: AppNode; config: TConfig & { @@ -1142,11 +1152,11 @@ export interface ExtensionBlueprint< ReturnType >; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable; } & VerifyExtensionFactoryOutput< - UOutput & UExtraOutput, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, UFactoryOutput >) | { @@ -1186,7 +1196,17 @@ export interface ExtensionBoundaryProps { // @public (undocumented) export type ExtensionDataContainer = - { + Iterable< + UExtensionData extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? IConfig['optional'] extends true + ? never + : ExtensionDataValue + : never + > & { get( ref: ExtensionDataRef, ): UExtensionData extends ExtensionDataRef @@ -1356,7 +1376,6 @@ export const IconBundleBlueprint: ExtensionBlueprint< } >; }, - AnyExtensionDataRef, { icons: string; test: string; From 2212c3ef08c63cd54ef19333200c63da9e7d895f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Aug 2024 17:32:28 +0200 Subject: [PATCH 067/393] frontend-plugin-api: clean up output verification Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.ts | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 2c82c44ee1..2a29fe1ab4 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -158,9 +158,7 @@ export interface LegacyCreateExtensionOptions< /** @ignore */ export type VerifyExtensionFactoryOutput< UDeclaredOutput extends AnyExtensionDataRef, - UFactoryOutput extends - | ExtensionDataValue - | ExtensionDataContainer, + UFactoryOutput extends ExtensionDataValue, > = ( UDeclaredOutput extends any ? UDeclaredOutput['config']['optional'] extends true @@ -168,29 +166,21 @@ export type VerifyExtensionFactoryOutput< : UDeclaredOutput['id'] : never ) extends infer IRequiredOutputIds - ? ( - UFactoryOutput extends ExtensionDataValue - ? UFactoryOutput['id'] - : UFactoryOutput extends ExtensionDataContainer - ? IDataRefs['id'] - : never - ) extends infer IFactoryOutputIds - ? [IRequiredOutputIds] extends [IFactoryOutputIds] - ? [IFactoryOutputIds] extends [UDeclaredOutput['id']] - ? {} - : { - 'Error: The extension factory has undeclared output(s)': Exclude< - IFactoryOutputIds, - UDeclaredOutput['id'] - >; - } + ? [IRequiredOutputIds] extends [UFactoryOutput['id']] + ? [UFactoryOutput['id']] extends [UDeclaredOutput['id']] + ? {} : { - 'Error: The extension factory is missing the following output(s)': Exclude< - IRequiredOutputIds, - IFactoryOutputIds + 'Error: The extension factory has undeclared output(s)': Exclude< + UFactoryOutput['id'], + UDeclaredOutput['id'] >; } - : never + : { + 'Error: The extension factory is missing the following output(s)': Exclude< + IRequiredOutputIds, + UFactoryOutput['id'] + >; + } : never; /** @public */ From 67f3a3932f857d5bf20b93e6fc3fb01c5adeb5b6 Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Tue, 6 Aug 2024 18:09:27 +0200 Subject: [PATCH 068/393] fix: add type dep Signed-off-by: Chris Langhout --- packages/backend-defaults/package.json | 1 + .../src/entrypoints/database/connectors/postgres.ts | 2 +- yarn.lock | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 3341c96c75..38d703e2b4 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -141,6 +141,7 @@ "@opentelemetry/api": "^1.3.0", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", + "@types/pg-format": "^1.0.5", "archiver": "^6.0.0", "base64-stream": "^1.0.0", "better-sqlite3": "^11.0.0", diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index afaa946c05..46b958789d 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -29,7 +29,7 @@ import { Connector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; -import { format } from 'pg-format'; +import format from 'pg-format'; // Limits the number of concurrent DDL operations to 1 const ddlLimiter = limiterFactory(1); diff --git a/yarn.lock b/yarn.lock index 75552e383b..2da7805693 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3649,6 +3649,7 @@ __metadata: "@types/http-errors": ^2.0.0 "@types/morgan": ^1.9.0 "@types/node-forge": ^1.3.0 + "@types/pg-format": ^1.0.5 "@types/stoppable": ^1.1.0 archiver: ^6.0.0 aws-sdk-client-mock: ^4.0.0 @@ -18593,6 +18594,13 @@ __metadata: languageName: node linkType: hard +"@types/pg-format@npm:^1.0.5": + version: 1.0.5 + resolution: "@types/pg-format@npm:1.0.5" + checksum: e7907b1e478b54aec581d14bc4df349d40d6e30e1f03605e9d162f515f35064e9deaeeb563cb812adfac271522c7ed68b13ebb6e2036d557c5533cf6357419bd + languageName: node + linkType: hard + "@types/pg-pool@npm:2.0.4": version: 2.0.4 resolution: "@types/pg-pool@npm:2.0.4" From 8543e723a5d40b61e7fb32bd5f6d6c9f0df1883a Mon Sep 17 00:00:00 2001 From: Sydney Achinger <78113809+squid-ney@users.noreply.github.com> Date: Tue, 6 Aug 2024 20:55:39 -0400 Subject: [PATCH 069/393] Add UI element to notify user before redirect occurs. (#25911) * Add UI element to notify user before redirect occurs. Allow user to skip to redirect from notification. --------- Signed-off-by: Sydney Achinger --- .changeset/popular-panthers-hear.md | 5 + .../transformers/handleMetaRedirects.test.ts | 30 ++++- .../transformers/handleMetaRedirects.ts | 59 -------- .../transformers/handleMetaRedirects.tsx | 126 ++++++++++++++++++ 4 files changed, 160 insertions(+), 60 deletions(-) create mode 100644 .changeset/popular-panthers-hear.md delete mode 100644 plugins/techdocs/src/reader/transformers/handleMetaRedirects.ts create mode 100644 plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx diff --git a/.changeset/popular-panthers-hear.md b/.changeset/popular-panthers-hear.md new file mode 100644 index 0000000000..c31586ea82 --- /dev/null +++ b/.changeset/popular-panthers-hear.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +TechDocs redirect feature now includes a notification to the user before they are redirected. diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts index 8fbd63428d..12a552577a 100644 --- a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts +++ b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts @@ -16,6 +16,7 @@ import { handleMetaRedirects } from './handleMetaRedirects'; import { createTestShadowDom } from '../../test-utils'; +import { screen } from '@testing-library/react'; describe('handleMetaRedirects', () => { const navigate = jest.fn(); @@ -35,15 +36,19 @@ describe('handleMetaRedirects', () => { }, writable: true, }); - return await createTestShadowDom(html, { preTransformers: [], postTransformers: [handleMetaRedirects(navigate, entityName)], }); }; + beforeEach(() => { + jest.useFakeTimers(); + }); afterEach(() => { jest.clearAllMocks(); + jest.useRealTimers(); + document.body.innerHTML = ''; }); it('should navigate to relative URL if meta redirect tag is present', async () => { @@ -52,6 +57,13 @@ describe('handleMetaRedirects', () => { 'http://localhost/docs/default/component/testEntity/subpath', '/docs/default/component/testEntity/subpath', ); + + expect( + await screen.findByText( + 'This TechDocs page is no longer maintained. Will automatically redirect to the designated replacement.', + ), + ).toBeInTheDocument(); + jest.runAllTimers(); expect(navigate).toHaveBeenCalledWith( 'http://localhost/docs/default/component/testEntity/anotherPage', ); @@ -63,6 +75,13 @@ describe('handleMetaRedirects', () => { 'http://localhost/docs/default/component/testEntity/subpath', '/docs/default/component/testEntity/subpath', ); + + expect( + await screen.findByText( + 'This TechDocs page is no longer maintained. Will automatically redirect to the designated replacement.', + ), + ).toBeInTheDocument(); + jest.runAllTimers(); expect(navigate).toHaveBeenCalledWith('/docs/default/component/testEntity'); }); @@ -72,6 +91,13 @@ describe('handleMetaRedirects', () => { 'http://localhost/docs/default/component/testEntity/subpath', '/docs/default/component/testEntity/subpath', ); + + expect( + await screen.findByText( + 'This TechDocs page is no longer maintained. Will automatically redirect to the designated replacement.', + ), + ).toBeInTheDocument(); + jest.runAllTimers(); expect(navigate).toHaveBeenCalledWith('http://localhost/test'); }); @@ -81,6 +107,8 @@ describe('handleMetaRedirects', () => { 'http://localhost/docs/default/component/testEntity/subpath', '/docs/default/component/testEntity/subpath', ); + + jest.runAllTimers(); expect(navigate).not.toHaveBeenCalled(); }); }); diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.ts b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.ts deleted file mode 100644 index 4c56d4d363..0000000000 --- a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Transformer } from './transformer'; -import { normalizeUrl } from './rewriteDocLinks'; - -export const handleMetaRedirects = ( - navigate: (to: string) => void, - entityName: string, -): Transformer => { - return dom => { - for (const elem of Array.from(dom.querySelectorAll('meta'))) { - if (elem.getAttribute('http-equiv') === 'refresh') { - const metaContentParameters = elem - .getAttribute('content') - ?.split('url='); - if (!metaContentParameters || metaContentParameters.length < 2) { - continue; - } - - const metaUrl = metaContentParameters[1]; - const normalizedCurrentUrl = normalizeUrl(window.location.href); - // If metaUrl is relative, it will be resolved with base href. If it is absolute, it will replace the base href when creating URL object. - const absoluteRedirectObj = new URL(metaUrl, normalizedCurrentUrl); - const isExternalRedirect = - absoluteRedirectObj.hostname !== window.location.hostname; - - if (isExternalRedirect) { - // If the redirect is external, navigate to the documentation site home instead of the external url. - const currentTechDocPath = window.location.pathname; - const indexOfSiteHome = currentTechDocPath.indexOf(entityName); - const siteHomePath = currentTechDocPath.slice( - 0, - indexOfSiteHome + entityName.length, - ); - navigate(siteHomePath); - } else { - // The navigate function from dom.tsx is a wrapper around react-router navigate function that helps absolute url redirects. - navigate(absoluteRedirectObj.href); - } - return dom; - } - } - return dom; - }; -}; diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx new file mode 100644 index 0000000000..2d74f4cbf5 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx @@ -0,0 +1,126 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transformer } from './transformer'; +import { normalizeUrl } from './rewriteDocLinks'; +import Snackbar from '@material-ui/core/Snackbar'; +import React, { useState } from 'react'; +import { renderReactElement } from './renderReactElement'; +import Button from '@material-ui/core/Button'; +import { makeStyles } from '@material-ui/core/styles'; + +type RedirectNotificationProps = { + handleButtonClick: () => void; + message: string; + autoHideDuration: number; +}; + +const useStyles = makeStyles(theme => ({ + button: { + color: theme.palette.primary.light, + textDecoration: 'underline', + }, +})); +const RedirectNotification = ({ + message, + handleButtonClick, + autoHideDuration, +}: RedirectNotificationProps) => { + const classes = useStyles(); + const [open, setOpen] = useState(true); + const handleClose = () => { + setOpen(prev => !prev); + }; + + return ( + + Redirect now + + } + /> + ); +}; + +export const handleMetaRedirects = ( + navigate: (to: string) => void, + entityName: string, +): Transformer => { + const redirectAfterMs = 4000; + const determineRedirectURL = (metaUrl: string) => { + const normalizedCurrentUrl = normalizeUrl(window.location.href); + // If metaUrl is relative, it will be resolved with base href. If it is absolute, it will replace the base href when creating URL object. + const absoluteRedirectObj = new URL(metaUrl, normalizedCurrentUrl); + const isExternalRedirect = + absoluteRedirectObj.hostname !== window.location.hostname; + + if (isExternalRedirect) { + const currentTechDocPath = window.location.pathname; + const indexOfSiteHome = currentTechDocPath.indexOf(entityName); + const siteHomePath = currentTechDocPath.slice( + 0, + indexOfSiteHome + entityName.length, + ); + return siteHomePath; + } + // The navigate function from dom.tsx is a wrapper around react-router navigate function that helps absolute url redirects. + return absoluteRedirectObj.href; + }; + + return dom => { + for (const elem of Array.from(dom.querySelectorAll('meta'))) { + if (elem.getAttribute('http-equiv') === 'refresh') { + const metaContentParameters = elem + .getAttribute('content') + ?.split('url='); + if (!metaContentParameters || metaContentParameters.length < 2) { + continue; + } + const metaUrl = metaContentParameters[1]; + const redirectURL = determineRedirectURL(metaUrl); + const container = document.createElement('div'); + + renderReactElement( + navigate(redirectURL)} + autoHideDuration={redirectAfterMs} + />, + container, + ); + document.body.appendChild(container); + + setTimeout(() => { + navigate(redirectURL); + }, redirectAfterMs); + + return dom; + } + } + return dom; + }; +}; From d18f4eba43f6fe589c09b62b47a3bb9fe099be23 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 7 Aug 2024 11:24:46 +0530 Subject: [PATCH 070/393] scaffolder: remove undefined from the title of Scaffolder Run page Signed-off-by: Himanshu Mishra --- .changeset/real-lizards-sit.md | 5 +++++ .../scaffolder/src/components/OngoingTask/OngoingTask.tsx | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/real-lizards-sit.md diff --git a/.changeset/real-lizards-sit.md b/.changeset/real-lizards-sit.md new file mode 100644 index 0000000000..747f041508 --- /dev/null +++ b/.changeset/real-lizards-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Fix undefined in the title of Scaffolder Runs on the page load diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 31c2870352..459ba682d2 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -173,7 +173,9 @@ export const OngoingTask = (props: { return (
Run of {templateName} From b9cd50536fa43f1c9c8ac62a819c2db826f526a7 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 11:21:31 +0200 Subject: [PATCH 071/393] chore: fix Signed-off-by: blam Signed-off-by: blam --- .../src/wiring/createExtensionBlueprint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 2b80dac81e..88b2523111 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -420,7 +420,7 @@ export function createExtensionBlueprint< ): ExtensionBlueprint< TParams, UOutput, - TInputs, + string extends keyof TInputs ? {} : TInputs, string extends keyof TConfigSchema ? {} : { [key in keyof TConfigSchema]: z.infer> }, @@ -436,7 +436,7 @@ export function createExtensionBlueprint< return new ExtensionBlueprintImpl(options) as ExtensionBlueprint< TParams, UOutput, - TInputs, + string extends keyof TInputs ? {} : TInputs, string extends keyof TConfigSchema ? {} : { From 6f72c2b9905c86fc7cf4d30ced9887facf017e58 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 11:22:37 +0200 Subject: [PATCH 072/393] chore: updating changeset Signed-off-by: blam --- .changeset/breezy-cats-kiss.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/breezy-cats-kiss.md diff --git a/.changeset/breezy-cats-kiss.md b/.changeset/breezy-cats-kiss.md new file mode 100644 index 0000000000..3bcbfaf00a --- /dev/null +++ b/.changeset/breezy-cats-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Fixing issue with `Blueprints` `inputs` merging From 9a0147e6daa458ecd1add64d1a20ed3c48781b24 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 11:39:13 +0200 Subject: [PATCH 073/393] chore: fixing api reports Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 38b980fcbf..4dc9c99c78 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -622,7 +622,7 @@ export function createExtensionBlueprint< ): ExtensionBlueprint< TParams, UOutput, - TInputs, + string extends keyof TInputs ? {} : TInputs, string extends keyof TConfigSchema ? {} : { @@ -1367,15 +1367,7 @@ export const IconBundleBlueprint: ExtensionBlueprint< 'core.icons', {} >, - { - [x: string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - }, + {}, { icons: string; test: string; From f4612847a2090aec61e05798f7a758328c4df3c1 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Tue, 6 Aug 2024 16:05:57 +0200 Subject: [PATCH 074/393] Add install instructions for older nevironments Signed-off-by: Marek Libra --- docs/notifications/getting-started.md | 104 +++++++++++++++++++++++- plugins/notifications-backend/README.md | 12 +-- plugins/notifications/README.md | 35 +------- plugins/signals-backend/README.md | 39 +-------- plugins/signals/README.md | 18 +--- 5 files changed, 107 insertions(+), 101 deletions(-) diff --git a/docs/notifications/getting-started.md b/docs/notifications/getting-started.md index 583be64cc8..dc006e21b2 100644 --- a/docs/notifications/getting-started.md +++ b/docs/notifications/getting-started.md @@ -6,7 +6,7 @@ description: How to get started with the notifications and signals The Backstage Notifications System provides a way for plugins and external services to send notifications to Backstage users. These notifications are displayed in the dedicated page of the Backstage frontend UI or by frontend plugins per specific scenarios. -Additionally, plugins can implement processors to send notifications through external channels like email, Slack, or MS Teams. +Additionally, notifications can be sent to external channels (like email) via "processors" implemented within plugins. Notifications can be optionally integrated with the signals (a push mechanism) to ensure users receive them immediately. @@ -31,6 +31,104 @@ Example of use-cases: - Notifications for individuals: e.g., updates you have subscribed to, new required training courses - Notifications pertaining to a particular entity in the catalog: A notification might apply to an entity and the owning team. +## Installation in Older Environments + +Newer versions of instances created by the create-app have both the notifications and signals plugins included by default, this section can be skipped right to the Configuration. + +Following installation instructions are valid for enabling the plugins in older environments. + +### Add Notifications Backend + +```bash +yarn workspace backend add @backstage/plugin-notifications-backend +``` + +Add the notifications to your `backend/src/index.ts`: + +```ts +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-notifications-backend')); +``` + +### Add Notifications Frontend + +```bash +yarn workspace app add @backstage/notifications +``` + +To add the notifications main menu, add following to your `packages/app/src/components/Root/Root.tsx`: + +```tsx +import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; + + + + + // ... + + + +; +``` + +Also add the route to notifications to `packages/app/src/App.tsx`: + +```tsx +import { NotificationsPage } from '@backstage/plugin-notifications'; + + + // ... + } /> +; +``` + +### Optional: Add Signals Backend + +Optionally add Signals to your backend by + +```bash +yarn workspace backend add @backstage/plugin-signals-backend +``` + +Add the signals to your `backend/src/index.ts`: + +```ts +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-signals-backend')); +``` + +### Optional: Signals Frontend + +The use of signals is optional but improves user experience. + +Start with: + +```bash +yarn workspace app add @backstage/plugin-signals +``` + +To install the plugin, you have to add the following to your `packages/app/src/plugins.ts`: + +```ts +export { signalsPlugin } from '@backstage/plugin-signals'; +``` + +And make sure that your `packages/app/src/App.tsx` contains: + +```ts +import * as plugins from './plugins'; + +const app = createApp({ + // ... + plugins: Object.values(plugins), + // ... +}); +``` + +If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used. + ## Configuration ### Notifications Backend @@ -209,7 +307,9 @@ curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications -H "Content-Type: app ## Additional info -Additional details can be found in the plugins' implementation: +An example of a backend plugin sending notifications can be found in https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications. + +Sources of the notifications and signal plugins: - https://github.com/backstage/backstage/blob/master/plugins/notifications diff --git a/plugins/notifications-backend/README.md b/plugins/notifications-backend/README.md index ebd129f572..a6b6615d9f 100644 --- a/plugins/notifications-backend/README.md +++ b/plugins/notifications-backend/README.md @@ -4,17 +4,7 @@ Welcome to the notifications backend plugin! ## Getting started -```bash -yarn workspace backend add @backstage/notifications-backend -``` - -Add the notifications to your backend: - -```ts -const backend = createBackend(); -// ... -backend.add(import('@backstage/plugin-notifications-backend')); -``` +To install, please refer the [Getting Started](https://backstage.io/docs/notifications) Backstage Notifications and Signals documentation section. For users to be able to see notifications in real-time, you have to install also the signals plugin (`@backstage/plugin-signals-node`, `@backstage/plugin-signals-backend`, and diff --git a/plugins/notifications/README.md b/plugins/notifications/README.md index 32fbb8c701..78bcf4e753 100644 --- a/plugins/notifications/README.md +++ b/plugins/notifications/README.md @@ -4,40 +4,9 @@ Welcome to the notifications plugin! ## Getting started -First, install the `@backstage/plugin-notifications-backend` and `@backstage/plugin-notifications-node` packages. -See the documentation for installation instructions. +To install, please refer the [Getting Started](https://backstage.io/docs/notifications) Backstage Notifications and Signals documentation section. -Then add this frontend package: - -```bash -yarn workspace app add @backstage/notifications -``` - -To add the notifications main menu, add the following to your `packages/app/src/components/Root/Root.tsx`: - -```tsx -import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; - - - - - // ... - - - -; -``` - -Also add the route to notifications to `packages/app/src/App.tsx`: - -```tsx -import { NotificationsPage } from '@backstage/plugin-notifications'; - - - // ... - } /> -; -``` +Please mind installing the `@backstage/plugin-notifications-backend` and `@backstage/plugin-notifications-node` packages before this frontend plugin. ## Real-time notifications diff --git a/plugins/signals-backend/README.md b/plugins/signals-backend/README.md index d76038a38e..24d8832d26 100644 --- a/plugins/signals-backend/README.md +++ b/plugins/signals-backend/README.md @@ -6,41 +6,4 @@ Signals plugin allows backend plugins to publish messages to frontend plugins. ## Getting started -First install the `@backstage/plugin-signals-node` plugin to get the `SignalsService` set up. - -Next, add Signals router to your backend in `packages/backend/src/plugins/signals.ts`: - -```ts -import { Router } from 'express'; -import { createRouter } from '@backstage/plugin-signals-backend'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - eventBroker: env.eventBroker, - identity: env.identity, - discovery: env.discovery, - }); -} -``` - -Now add the signals to `packages/backend/src/index.ts`: - -```ts -// ... -import signals from './plugins/signals'; - -async function main() { - // ... - const signalsEnv = useHotMemoize(module, () => createEnv('signals')); - - const apiRouter = Router(); - // ... - apiRouter.use('/signals', await signals(signalsEnv)); - apiRouter.use(notFoundHandler()); - // ... -} -``` +To install this signals backend plugin, please refer the [Getting Started](https://backstage.io/docs/notifications) Backstage Notifications and Signals documentation section. diff --git a/plugins/signals/README.md b/plugins/signals/README.md index 38dc18808b..5b08983621 100644 --- a/plugins/signals/README.md +++ b/plugins/signals/README.md @@ -9,23 +9,7 @@ Signals plugin allows backend plugins to publish messages to frontend plugins. This plugin contains client that can receive messages from the backend. To get started, see installation instructions from `@backstage/plugin-signals-node`, `@backstage/plugin-signals-backend`. -To install the plugin, you have to add the following to your `packages/app/src/plugins.ts`: - -```ts -export { signalsPlugin } from '@backstage/plugin-signals'; -``` - -And make sure that your `packages/app/src/App.tsx` contains: - -```ts -import * as plugins from './plugins'; - -const app = createApp({ - // ... - plugins: Object.values(plugins), - // ... -}); -``` +To install this signals frontend plugin, please refer the [Getting Started](https://backstage.io/docs/notifications) Backstage Notifications and Signals documentation section. Now you can utilize the API from other plugins using the `@backstage/plugin-signals-react` package or simply by: From 3a8e23d8ff1b03bc1caaae1441b7fb828f67e5b1 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 11:47:47 +0200 Subject: [PATCH 075/393] chore: updating changeset wording Signed-off-by: blam --- .changeset/breezy-cats-kiss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/breezy-cats-kiss.md b/.changeset/breezy-cats-kiss.md index 3bcbfaf00a..900016c1e8 100644 --- a/.changeset/breezy-cats-kiss.md +++ b/.changeset/breezy-cats-kiss.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': patch --- -Fixing issue with `Blueprints` `inputs` merging +Fixing issue with extension blueprints `inputs` merging. From 3fbf4a8719270ed227f8496ec0edeebca7abd271 Mon Sep 17 00:00:00 2001 From: Evan Kelly <47178446+EvanGKelly@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:13:29 +0100 Subject: [PATCH 076/393] Update docs/integrations/datadog-rum/installation.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Evan Kelly <47178446+EvanGKelly@users.noreply.github.com> --- docs/integrations/datadog-rum/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 414317d593..8213b8296f 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -28,7 +28,7 @@ app: If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. -Please note, there's a bug where env value MUST be specified at build time +Please note that the env value MUST be specified at build time :::note In case after a proper configuration, the events still arent being capture copy and paste this section in to your `packages/app/public/index.html` under the `` tag. From 807bd071759be7e431d09c300839177498850bc9 Mon Sep 17 00:00:00 2001 From: Evan Kelly <47178446+EvanGKelly@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:13:39 +0100 Subject: [PATCH 077/393] Update docs/integrations/datadog-rum/installation.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Evan Kelly <47178446+EvanGKelly@users.noreply.github.com> --- docs/integrations/datadog-rum/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 8213b8296f..6659bec182 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -31,7 +31,7 @@ If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8 Please note that the env value MUST be specified at build time :::note -In case after a proper configuration, the events still arent being capture copy and paste this section in to your `packages/app/public/index.html` under the `` tag. +In case after a proper configuration, the events still are not being captured: Copy and paste this section in to your `packages/app/public/index.html` under the `` tag. ```html <% if (config.has('app.datadogRum')) { %> From ddde5fe772b3cb910fa8397db67b8fe98472e135 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 7 Aug 2024 12:48:09 +0200 Subject: [PATCH 078/393] backend-plugin-api: fix for plugins and modules depending on multion services Signed-off-by: Patrik Oldsberg --- .changeset/seven-days-film.md | 5 ++ .changeset/silver-pillows-begin.md | 5 ++ .../config/vocabularies/Backstage/accept.txt | 1 + .../src/wiring/BackendInitializer.test.ts | 56 +++++++++++++++++++ .../src/compat/legacy/legacy.ts | 8 ++- packages/backend-plugin-api/api-report.md | 20 +++---- .../src/wiring/createBackendModule.test.ts | 33 +++++++++++ .../src/wiring/createBackendPlugin.test.ts | 48 ++++++++++++++++ .../backend-plugin-api/src/wiring/types.ts | 35 ++++++++---- 9 files changed, 187 insertions(+), 24 deletions(-) create mode 100644 .changeset/seven-days-film.md create mode 100644 .changeset/silver-pillows-begin.md diff --git a/.changeset/seven-days-film.md b/.changeset/seven-days-film.md new file mode 100644 index 0000000000..9d757b0611 --- /dev/null +++ b/.changeset/seven-days-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. diff --git a/.changeset/silver-pillows-begin.md b/.changeset/silver-pillows-begin.md new file mode 100644 index 0000000000..8fe1ccc230 --- /dev/null +++ b/.changeset/silver-pillows-begin.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Internal type refactor. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 42b0e4895c..bad61de7ff 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -245,6 +245,7 @@ Monorepo monorepos msgraph msw +multiton mutex mutexes mysql diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 6f60d65861..cf8845fcec 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -342,6 +342,62 @@ describe('BackendInitializer', () => { await init.start(); }); + it('should allow plugins and modules depend on multiton services', async () => { + expect.assertions(2); + + const multiServiceRef = createServiceRef({ + id: 'a', + multiton: true, + }); + const init = new BackendInitializer(baseFactories); + + init.add( + createServiceFactory({ + service: multiServiceRef, + deps: {}, + factory: () => 'x', + }), + ); + init.add( + createServiceFactory({ + service: multiServiceRef, + deps: {}, + factory: () => 'y', + }), + ); + + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: { multi: multiServiceRef }, + async init({ multi }) { + expect(multi).toEqual(['x', 'y']); + }, + }); + }, + }), + ); + + init.add( + createBackendModule({ + pluginId: 'test', + moduleId: 'test', + register(reg) { + reg.registerInit({ + deps: { multi: multiServiceRef }, + async init({ multi }) { + expect(multi).toEqual(['x', 'y']); + }, + }); + }, + }), + ); + + await init.start(); + }); + it('should forward errors when plugins fail to start', async () => { const init = new BackendInitializer([]); init.add( diff --git a/packages/backend-common/src/compat/legacy/legacy.ts b/packages/backend-common/src/compat/legacy/legacy.ts index c779daacd2..4dc7d1a09a 100644 --- a/packages/backend-common/src/compat/legacy/legacy.ts +++ b/packages/backend-common/src/compat/legacy/legacy.ts @@ -18,6 +18,7 @@ import { AuthService, coreServices, createBackendPlugin, + HttpRouterService, ServiceRef, } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; @@ -107,7 +108,10 @@ export function makeLegacyPlugin< return [key, transform(dep)]; } if (key === 'tokenManager') { - return [key, wrapTokenManager(dep as TokenManager, _auth)]; + return [ + key, + wrapTokenManager(dep as TokenManager, _auth as AuthService), + ]; } return [key, dep]; }), @@ -115,7 +119,7 @@ export function makeLegacyPlugin< const router = await createRouter( pluginEnv as TransformedEnv, ); - _router.use(router); + (_router as HttpRouterService).use(router); }, }); }, diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index e5189dce4d..319ddf0bc6 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -82,14 +82,12 @@ export interface BackendModuleRegistrationPoints { ): void; // (undocumented) registerInit< - Deps extends { - [name in string]: unknown; + TDeps extends { + [name in string]: ServiceRef | ExtensionPoint; }, >(options: { - deps: { - [name in keyof Deps]: ServiceRef | ExtensionPoint; - }; - init(deps: Deps): Promise; + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } @@ -105,14 +103,12 @@ export interface BackendPluginRegistrationPoints { ): void; // (undocumented) registerInit< - Deps extends { - [name in string]: unknown; + TDeps extends { + [name in string]: ServiceRef; }, >(options: { - deps: { - [name in keyof Deps]: ServiceRef; - }; - init(deps: Deps): Promise; + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts index e3c9f9007a..3426d5a90c 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts @@ -14,7 +14,9 @@ * limitations under the License. */ +import { createServiceRef } from '../services'; import { createBackendModule } from './createBackendModule'; +import { createExtensionPoint } from './createExtensionPoint'; import { InternalBackendRegistrations } from './types'; describe('createBackendModule', () => { @@ -54,4 +56,35 @@ describe('createBackendModule', () => { // @ts-expect-error expect(module({ a: 'a' })).toBeDefined(); }); + + it('should be able to depend on all types of dependencies', () => { + const extensionPoint = createExtensionPoint({ id: 'point' }); + const singleServiceRef = createServiceRef({ id: 'single' }); + const multiServiceRef = createServiceRef({ + id: 'multi', + multiton: true, + }); + + const plugin = createBackendModule({ + pluginId: 'x', + moduleId: 'y', + register(r) { + r.registerInit({ + deps: { + point: extensionPoint, + single: singleServiceRef, + multi: multiServiceRef, + }, + async init({ point, single, multi }) { + const a: string = point; + const b: string = single; + const c: string[] = multi; + expect([a, b, c]).toBe('unused'); + }, + }); + }, + }); + + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + }); }); diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts index db4d60ee19..f4a0877b85 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts @@ -14,7 +14,9 @@ * limitations under the License. */ +import { createServiceRef } from '../services'; import { createBackendPlugin } from './createBackendPlugin'; +import { createExtensionPoint } from './createExtensionPoint'; import { InternalBackendRegistrations } from './types'; describe('createBackendPlugin', () => { @@ -52,4 +54,50 @@ describe('createBackendPlugin', () => { // @ts-expect-error expect(plugin({ a: 'a' })).toBeDefined(); }); + + it('should be able to depend on all compatible dependencies', () => { + const singleServiceRef = createServiceRef({ id: 'single' }); + const multiServiceRef = createServiceRef({ + id: 'multi', + multiton: true, + }); + + const plugin = createBackendPlugin({ + pluginId: 'x', + register(r) { + r.registerInit({ + deps: { + single: singleServiceRef, + multi: multiServiceRef, + }, + async init({ single, multi }) { + const a: string = single; + const b: string[] = multi; + expect([a, b]).toBe('unused'); + }, + }); + }, + }); + + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + }); + + it('should not be able to depend on extension points', () => { + const extensionPoint = createExtensionPoint({ id: 'point' }); + + const plugin = createBackendPlugin({ + pluginId: 'x', + register(r) { + r.registerInit({ + deps: { + // @ts-expect-error + point: extensionPoint, + }, + async init() {}, + }); + }, + }); + + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + }); }); diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts index 01ab8c2e3e..1e8c3910a3 100644 --- a/packages/backend-plugin-api/src/wiring/types.ts +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -36,6 +36,17 @@ export type ExtensionPoint = { $$type: '@backstage/ExtensionPoint'; }; +/** @ignore */ +type DepsToInstances< + TDeps extends { + [key in string]: ServiceRef | ExtensionPoint; + }, +> = { + [key in keyof TDeps]: TDeps[key] extends ServiceRef + ? Array + : TDeps[key]['T']; +}; + /** * The callbacks passed to the `register` method of a backend plugin. * @@ -46,11 +57,13 @@ export interface BackendPluginRegistrationPoints { ref: ExtensionPoint, impl: TExtensionPoint, ): void; - registerInit(options: { - deps: { - [name in keyof Deps]: ServiceRef; - }; - init(deps: Deps): Promise; + registerInit< + TDeps extends { + [name in string]: ServiceRef; + }, + >(options: { + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } @@ -64,11 +77,13 @@ export interface BackendModuleRegistrationPoints { ref: ExtensionPoint, impl: TExtensionPoint, ): void; - registerInit(options: { - deps: { - [name in keyof Deps]: ServiceRef | ExtensionPoint; - }; - init(deps: Deps): Promise; + registerInit< + TDeps extends { + [name in string]: ServiceRef | ExtensionPoint; + }, + >(options: { + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } From aa5149806e3583b67f166dd36ba6fa472bcec4ed Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Wed, 7 Aug 2024 12:29:16 +0200 Subject: [PATCH 079/393] fix: move @types deps to dev-dependencies Signed-off-by: Chris Langhout --- packages/backend-defaults/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 38d703e2b4..4ac9a759c0 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -141,7 +141,6 @@ "@opentelemetry/api": "^1.3.0", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", - "@types/pg-format": "^1.0.5", "archiver": "^6.0.0", "base64-stream": "^1.0.0", "better-sqlite3": "^11.0.0", @@ -193,6 +192,7 @@ "@types/http-errors": "^2.0.0", "@types/morgan": "^1.9.0", "@types/node-forge": "^1.3.0", + "@types/pg-format": "^1.0.5", "@types/stoppable": "^1.1.0", "aws-sdk-client-mock": "^4.0.0", "http-errors": "^2.0.0", From 66523e8d01fbb57e35bcfd164c20b11ba7fcae26 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 Jul 2024 15:47:45 +0200 Subject: [PATCH 080/393] chore: wip Signed-off-by: blam --- packages/app-next/src/App.tsx | 48 ++++++- .../src/extensions/createPageExtension.tsx | 131 ++++++++++++++++++ plugins/home/src/alpha.tsx | 58 +++++++- 3 files changed, 223 insertions(+), 14 deletions(-) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index b57c680474..25d1aacf8f 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -20,6 +20,7 @@ import { pagesPlugin } from './examples/pagesPlugin'; import notFoundErrorPage from './examples/notFoundErrorPageExtension'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; import homePlugin, { + extensions, titleExtensionDataRef, } from '@backstage/plugin-home/alpha'; @@ -74,13 +75,46 @@ TODO: /* app.tsx */ -const homePageExtension = createExtension({ - name: 'myhomepage', - attachTo: { id: 'page:home', input: 'props' }, - output: { - children: coreExtensionData.reactElement, - title: titleExtensionDataRef, - }, +// const homePageExtension = createExtension({ +// name: 'myhomepage', +// attachTo: { id: 'page:home', input: 'props' }, +// output: { +// children: coreExtensionData.reactElement, +// title: titleExtensionDataRef, +// }, +// factory() { +// return { children: homePage, title: 'just a title' }; +// }, +// }); + +// const homePage = createPageExtension({ +// defaultPath: '/home', +// routeRef: rootRouteRef, +// inputs: { +// props: createExtensionInput( +// { +// children: coreExtensionData.reactElement.optional(), +// title: titleExtensionDataRef.optional(), +// }, + +// { +// singleton: true, +// optional: true, +// }, +// ), +// }, +// loader: ({ inputs }) => +// import('./components/').then(m => +// compatWrapper( +// , +// ), +// ), +// }); + +const homePageExtension = extensions.homePage.override({ factory() { return { children: homePage, title: 'just a title' }; }, diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index cd434a3ffc..b10901f1bd 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -22,6 +22,9 @@ import { createExtension, ResolvedExtensionInputs, AnyExtensionInputMap, + createExtensionBlueprint, + ExtensionInput, + AnyExtensionDataRef, } from '../wiring'; import { RouteRef } from '../routing'; import { Expand } from '../types'; @@ -95,3 +98,131 @@ export function createPageExtension< }, }); } + +export function createNewPageExtension< + TConfig extends { path: string }, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, +>( + options: ( + | { + defaultPath: string; + } + | { + configSchema: PortableSchema; + } + ) & { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TInputs; + routeRef?: RouteRef; + loader: (options: { + config: TConfig; + inputs: Expand>; + }) => Promise; + }, +): ExtensionDefinition { + const configSchema = + 'configSchema' in options + ? options.configSchema + : (createSchemaFromZod(z => + z.object({ path: z.string().default(options.defaultPath) }), + ) as PortableSchema); + + return createExtension({ + kind: 'page', + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' }, + configSchema, + inputs: options.inputs, + disabled: options.disabled, + output: [ + coreExtensionData.routePath, + coreExtensionData.reactElement, + coreExtensionData.routeRef.optional(), + ], + factory({ config, inputs, node }) { + const ExtensionComponent = lazy(() => + options + .loader({ config, inputs }) + .then(element => ({ default: () => element })), + ); + + const outputs = [ + coreExtensionData.routePath(config.path), + coreExtensionData.reactElement( + + + , + ), + ]; + + if (options.routeRef) { + return [...outputs, coreExtensionData.routeRef(options.routeRef)]; + } + + return outputs; + }, + }); +} +/** + * A blueprint for creating extensions for routable React page components. + * @public + */ +export const PageExtensionBlueprint = createExtensionBlueprint({ + kind: 'page', + attachTo: { id: 'app/routes', input: 'routes' }, + output: [ + coreExtensionData.routePath, + coreExtensionData.reactElement, + coreExtensionData.routeRef.optional(), + ], + config: { + schema: { + path: z => z.string().optional(), + }, + }, + factory( + { + defaultPath, + loader, + routeRef, + }: { + defaultPath?: string; + // TODO(blam) This type is impossible to type properly here as we don't have access + // to the input type generic. Maybe not a deal breaker though. + // It means we have to override the factory function in the `.make` method instead. + loader: (opts: unknown) => Promise; + routeRef?: RouteRef; + }, + { config, inputs, node }, + ) { + const ExtensionComponent = lazy(() => + loader({ config, inputs }).then(element => ({ default: () => element })), + ); + + // TODO(blam): this is a little awkward for optional returns. I wonder if we should be using generators or yield instead + // for a better API here. + const outputs = [ + coreExtensionData.routePath(config.path ?? defaultPath!), + coreExtensionData.reactElement( + + + , + ), + ]; + + if (routeRef) { + return [...outputs, coreExtensionData.routeRef(routeRef)]; + } + + return outputs; + }, +}); diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index b13c2a2e0e..109eea333e 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -25,6 +25,10 @@ import { createRouteRef, } from '@backstage/frontend-plugin-api'; import { compatWrapper } from '@backstage/core-compat-api'; +import { + PageExtensionBlueprint, + createNewPageExtension, +} from '@backstage/frontend-plugin-api/src/extensions/createPageExtension'; const rootRouteRef = createRouteRef(); @@ -35,15 +39,15 @@ export const titleExtensionDataRef = createExtensionDataRef().with({ id: 'title', }); -const homePage = createPageExtension({ +const homePage = createNewPageExtension({ defaultPath: '/home', routeRef: rootRouteRef, inputs: { props: createExtensionInput( - { - children: coreExtensionData.reactElement.optional(), - title: titleExtensionDataRef.optional(), - }, + [ + coreExtensionData.reactElement.optional(), + titleExtensionDataRef.optional(), + ], { singleton: true, @@ -55,13 +59,53 @@ const homePage = createPageExtension({ import('./components/').then(m => compatWrapper( , ), ), }); +homePage.override({ + factory() {}, +}); + +// const homePage2 = PageExtensionBlueprint.make({ +// inputs: { +// props: createExtensionInput( +// [ +// coreExtensionData.reactElement.optional(), +// titleExtensionDataRef.optional(), +// ], +// { +// singleton: true, +// optional: true, +// }, +// ), +// }, +// factory(origFactory, { config, inputs, node }) { +// return origFactory({ +// defaultPath: '/home', +// routeRef: rootRouteRef, +// loader: () => +// import('./components/').then(m => +// compatWrapper( +// , +// ), +// }, +// }); + +/** + * @alpha + * This will not be the way to export extensions eventually, + * will be something like `homePlugin.extensions.homePage` or `homePlugin.extensions.get('page')` + * Just haven't worked out a nice way to fix the types yet + */ +export const extensions = { homePage }; + /** * @alpha */ From f30705fc23121030aa875f11b2d0bf12c04cf795 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 31 Jul 2024 14:00:06 +0200 Subject: [PATCH 081/393] chore: some more exploration Signed-off-by: blam --- packages/app-next/src/App.tsx | 4 +- .../src/extensions/createPageExtension.tsx | 2 +- .../src/wiring/createExtension.ts | 94 ++++++++++++++++++- plugins/home/src/alpha.tsx | 4 - 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 25d1aacf8f..a33b2de8dc 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -115,8 +115,8 @@ TODO: // }); const homePageExtension = extensions.homePage.override({ - factory() { - return { children: homePage, title: 'just a title' }; + factory(origi) { + return origi(); }, }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index b10901f1bd..efe6ab54d9 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -127,7 +127,7 @@ export function createNewPageExtension< inputs: Expand>; }) => Promise; }, -): ExtensionDefinition { +) { const configSchema = 'configSchema' in options ? options.configSchema diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 2a29fe1ab4..6684323b51 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -20,6 +20,7 @@ import { Expand } from '../types'; import { AnyExtensionDataRef, ExtensionDataRef, + ExtensionDataRefToValue, ExtensionDataValue, } from './createExtensionDataRef'; import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput'; @@ -234,6 +235,79 @@ export interface ExtensionDefinition { readonly configSchema?: PortableSchema; } +export type OverrideExtensionOptions< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfig, + TConfigInput, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, +> = { + kind?: string; + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TInputs; + output: Array; + /** @deprecated - use `config.schema` instead */ + configSchema?: PortableSchema; + config?: { + schema: TConfigSchema; + }; + factory?( + originalFactory: (context?: { + config?: { + [key in keyof TConfigSchema]: z.infer>; + }; + inputs?: Expand>; + }) => Iterable>, + context: { + node: AppNode; + config: TConfig & + (string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }); + inputs: Expand>; + }, + ): Iterable; +} & VerifyExtensionFactoryOutput; + +/** @public */ +export type OverridableExtension< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfig, + TConfigInput, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, +> = { + override( + options: OverrideExtensionOptions< + UOutput, + TInputs, + TConfig, + TConfigInput, + TConfigSchema, + UFactoryOutput + >, + ): ExtensionDefinition; +}; + /** @internal */ export type InternalExtensionDefinition = ExtensionDefinition & @@ -329,7 +403,15 @@ export function createExtension< [key in keyof TConfigSchema]: ReturnType; }> >) ->; +> & + OverridableExtension< + UOutput, + TInputs, + TConfig, + TConfigInput, + TConfigSchema, + UFactoryOutput + >; /** * @public * @deprecated - use the array format of `output` instead, see TODO-doc-link @@ -409,7 +491,15 @@ export function createExtension< [key in keyof TConfigSchema]: ReturnType; }> >) -> { +> & + OverridableExtension< + UOutput, + TInputs, + TConfig, + TConfigInput, + TConfigSchema, + UFactoryOutput + > { const newConfigSchema = options.config?.schema; if (newConfigSchema && options.configSchema) { throw new Error(`Cannot provide both configSchema and config.schema`); diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index 109eea333e..31851726e5 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -66,10 +66,6 @@ const homePage = createNewPageExtension({ ), }); -homePage.override({ - factory() {}, -}); - // const homePage2 = PageExtensionBlueprint.make({ // inputs: { // props: createExtensionInput( From 665448b21fe97ed448894ddd226b850ad95b6647 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 31 Jul 2024 15:01:22 +0200 Subject: [PATCH 082/393] chore: overriding of config seems to work in typescript Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 31 +++++++++++++++++++ .../src/wiring/createExtension.ts | 26 +++++++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index ab6a6c19a5..8da3504434 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -561,4 +561,35 @@ describe('createExtension', () => { }), ).toMatchObject({ version: 'v2' }); }); + + describe('overrides', () => { + it('should allow overriding of the default factory', () => { + const testExtension = createExtension({ + namespace: 'test', + attachTo: { id: 'root', input: 'blob' }, + output: [stringDataRef], + config: { + schema: { + foo: z => z.string().optional(), + }, + }, + factory() { + return [stringDataRef('default')]; + }, + }); + + testExtension.override({ + config: { + schema: { + bar: z => z.string().optional(), + }, + }, + factory(_, { config }) { + return [stringDataRef(config.foo ?? config.bar ?? 'default')]; + }, + }); + + expect(true).toBe(true); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 6684323b51..f1db16b4d2 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -247,6 +247,10 @@ export type OverrideExtensionOptions< TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, + TConfigSchemaOverrides extends { + [key: string]: (zImpl: typeof z) => z.ZodType; + }, + UOutputOverrides extends AnyExtensionDataRef = UOutput, > = { kind?: string; namespace?: string; @@ -254,11 +258,11 @@ export type OverrideExtensionOptions< attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; - output: Array; + output?: Array; /** @deprecated - use `config.schema` instead */ configSchema?: PortableSchema; config?: { - schema: TConfigSchema; + schema: TConfigSchemaOverrides; }; factory?( originalFactory: (context?: { @@ -273,6 +277,10 @@ export type OverrideExtensionOptions< (string extends keyof TConfigSchema ? {} : { + [key in keyof TConfigSchemaOverrides]: z.infer< + ReturnType + >; + } & { [key in keyof TConfigSchema]: z.infer< ReturnType >; @@ -280,7 +288,7 @@ export type OverrideExtensionOptions< inputs: Expand>; }, ): Iterable; -} & VerifyExtensionFactoryOutput; +} & VerifyExtensionFactoryOutput; /** @public */ export type OverridableExtension< @@ -296,14 +304,19 @@ export type OverridableExtension< TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, > = { - override( + override< + TConfigSchemaOverrides extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + >( options: OverrideExtensionOptions< UOutput, TInputs, TConfig, TConfigInput, TConfigSchema, - UFactoryOutput + UFactoryOutput, + TConfigSchemaOverrides >, ): ExtensionDefinition; }; @@ -540,6 +553,9 @@ export function createExtension< parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); return `ExtensionDefinition{${parts.join(',')}}`; }, + override() { + // todo(blam): implement. + }, } as InternalExtensionDefinition< TConfig & (string extends keyof TConfigSchema From 40ffc63b7016cdc35d3a2c9d3ecdebf75b152916 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 31 Jul 2024 15:30:33 +0200 Subject: [PATCH 083/393] chore: added some more overrides part Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 52 ++++++++++++++++++- .../src/wiring/createExtension.ts | 23 +++++--- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 8da3504434..7f27df4947 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { coreExtensionData } from './coreExtensionData'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; @@ -563,7 +564,7 @@ describe('createExtension', () => { }); describe('overrides', () => { - it('should allow overriding of the default factory', () => { + it('should allow overriding of config and merging', () => { const testExtension = createExtension({ namespace: 'test', attachTo: { id: 'root', input: 'blob' }, @@ -591,5 +592,54 @@ describe('createExtension', () => { expect(true).toBe(true); }); + + it('should allow overriding of outputs', () => { + const testExtension = createExtension({ + namespace: 'test', + attachTo: { id: 'root', input: 'blob' }, + output: [stringDataRef], + inputs: { + test: createExtensionInput([stringDataRef], { singleton: true }), + }, + config: { + schema: { + foo: z => z.string().optional(), + }, + }, + factory({ inputs }) { + return [stringDataRef(inputs.test.get(stringDataRef))]; + }, + }); + + const override = testExtension.override({ + output: [numberDataRef], + factory(_, { inputs }) { + return [ + numberDataRef(inputs.test.get(stringDataRef).length), + stringDataRef('default'), + ]; + }, + }); + + // @ts-expect-error - should fail as the string output from previous is not provided + const override2 = testExtension.override({ + output: [numberDataRef], + factory(_, { inputs }) { + return [numberDataRef(inputs.test.get(stringDataRef).length)]; + }, + }); + + // @ts-expect-error - this should fail because string output should be merged? + const override3 = testExtension.override({ + output: [numberDataRef], + factory(_, { inputs }) { + return [stringDataRef(inputs.test.get(stringDataRef))]; + }, + }); + + unused(override, override2, override3); + + expect(true).toBe(true); + }); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index f1db16b4d2..3ec674f75f 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -246,11 +246,12 @@ export type OverrideExtensionOptions< TConfig, TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, - UFactoryOutput extends ExtensionDataValue, + UOriginalFactoryOutput extends ExtensionDataValue, TConfigSchemaOverrides extends { [key: string]: (zImpl: typeof z) => z.ZodType; }, - UOutputOverrides extends AnyExtensionDataRef = UOutput, + UOutputOverrides extends AnyExtensionDataRef, + UFactoryOverrideOutput extends ExtensionDataValue, > = { kind?: string; namespace?: string; @@ -262,7 +263,10 @@ export type OverrideExtensionOptions< /** @deprecated - use `config.schema` instead */ configSchema?: PortableSchema; config?: { - schema: TConfigSchemaOverrides; + schema: TConfigSchemaOverrides & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; }; factory?( originalFactory: (context?: { @@ -270,7 +274,7 @@ export type OverrideExtensionOptions< [key in keyof TConfigSchema]: z.infer>; }; inputs?: Expand>; - }) => Iterable>, + }) => Iterable, context: { node: AppNode; config: TConfig & @@ -287,8 +291,9 @@ export type OverrideExtensionOptions< }); inputs: Expand>; }, - ): Iterable; -} & VerifyExtensionFactoryOutput; + ): Iterable; + // todo(blam): need to verify that the outputs are meged properly. +} & VerifyExtensionFactoryOutput; /** @public */ export type OverridableExtension< @@ -308,6 +313,8 @@ export type OverridableExtension< TConfigSchemaOverrides extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, + UOutputOverrides extends AnyExtensionDataRef, + UFactoryOverrideOutput extends ExtensionDataValue, >( options: OverrideExtensionOptions< UOutput, @@ -316,7 +323,9 @@ export type OverridableExtension< TConfigInput, TConfigSchema, UFactoryOutput, - TConfigSchemaOverrides + TConfigSchemaOverrides, + UOutputOverrides, + UFactoryOverrideOutput >, ): ExtensionDefinition; }; From 33786b586e08ef27cea7beb4a1c6062bacc4fb89 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 31 Jul 2024 15:33:44 +0200 Subject: [PATCH 084/393] chore: reset some things Signed-off-by: blam --- packages/app-next/src/App.tsx | 52 +++---------- .../src/extensions/createPageExtension.tsx | 78 +------------------ plugins/home/src/alpha.tsx | 54 ++----------- 3 files changed, 18 insertions(+), 166 deletions(-) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index a33b2de8dc..b57c680474 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -20,7 +20,6 @@ import { pagesPlugin } from './examples/pagesPlugin'; import notFoundErrorPage from './examples/notFoundErrorPageExtension'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; import homePlugin, { - extensions, titleExtensionDataRef, } from '@backstage/plugin-home/alpha'; @@ -75,48 +74,15 @@ TODO: /* app.tsx */ -// const homePageExtension = createExtension({ -// name: 'myhomepage', -// attachTo: { id: 'page:home', input: 'props' }, -// output: { -// children: coreExtensionData.reactElement, -// title: titleExtensionDataRef, -// }, -// factory() { -// return { children: homePage, title: 'just a title' }; -// }, -// }); - -// const homePage = createPageExtension({ -// defaultPath: '/home', -// routeRef: rootRouteRef, -// inputs: { -// props: createExtensionInput( -// { -// children: coreExtensionData.reactElement.optional(), -// title: titleExtensionDataRef.optional(), -// }, - -// { -// singleton: true, -// optional: true, -// }, -// ), -// }, -// loader: ({ inputs }) => -// import('./components/').then(m => -// compatWrapper( -// , -// ), -// ), -// }); - -const homePageExtension = extensions.homePage.override({ - factory(origi) { - return origi(); +const homePageExtension = createExtension({ + name: 'myhomepage', + attachTo: { id: 'page:home', input: 'props' }, + output: { + children: coreExtensionData.reactElement, + title: titleExtensionDataRef, + }, + factory() { + return { children: homePage, title: 'just a title' }; }, }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index efe6ab54d9..27b6b53b63 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -23,8 +23,6 @@ import { ResolvedExtensionInputs, AnyExtensionInputMap, createExtensionBlueprint, - ExtensionInput, - AnyExtensionDataRef, } from '../wiring'; import { RouteRef } from '../routing'; import { Expand } from '../types'; @@ -99,79 +97,6 @@ export function createPageExtension< }); } -export function createNewPageExtension< - TConfig extends { path: string }, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -) { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ path: z.string().default(options.defaultPath) }), - ) as PortableSchema); - - return createExtension({ - kind: 'page', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' }, - configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: [ - coreExtensionData.routePath, - coreExtensionData.reactElement, - coreExtensionData.routeRef.optional(), - ], - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); - - const outputs = [ - coreExtensionData.routePath(config.path), - coreExtensionData.reactElement( - - - , - ), - ]; - - if (options.routeRef) { - return [...outputs, coreExtensionData.routeRef(options.routeRef)]; - } - - return outputs; - }, - }); -} /** * A blueprint for creating extensions for routable React page components. * @public @@ -208,7 +133,8 @@ export const PageExtensionBlueprint = createExtensionBlueprint({ loader({ config, inputs }).then(element => ({ default: () => element })), ); - // TODO(blam): this is a little awkward for optional returns. I wonder if we should be using generators or yield instead + // TODO(blam): this is a little awkward for optional returns. + // I wonder if we should be using generators or yield instead // for a better API here. const outputs = [ coreExtensionData.routePath(config.path ?? defaultPath!), diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index 31851726e5..b13c2a2e0e 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -25,10 +25,6 @@ import { createRouteRef, } from '@backstage/frontend-plugin-api'; import { compatWrapper } from '@backstage/core-compat-api'; -import { - PageExtensionBlueprint, - createNewPageExtension, -} from '@backstage/frontend-plugin-api/src/extensions/createPageExtension'; const rootRouteRef = createRouteRef(); @@ -39,15 +35,15 @@ export const titleExtensionDataRef = createExtensionDataRef().with({ id: 'title', }); -const homePage = createNewPageExtension({ +const homePage = createPageExtension({ defaultPath: '/home', routeRef: rootRouteRef, inputs: { props: createExtensionInput( - [ - coreExtensionData.reactElement.optional(), - titleExtensionDataRef.optional(), - ], + { + children: coreExtensionData.reactElement.optional(), + title: titleExtensionDataRef.optional(), + }, { singleton: true, @@ -59,49 +55,13 @@ const homePage = createNewPageExtension({ import('./components/').then(m => compatWrapper( , ), ), }); -// const homePage2 = PageExtensionBlueprint.make({ -// inputs: { -// props: createExtensionInput( -// [ -// coreExtensionData.reactElement.optional(), -// titleExtensionDataRef.optional(), -// ], -// { -// singleton: true, -// optional: true, -// }, -// ), -// }, -// factory(origFactory, { config, inputs, node }) { -// return origFactory({ -// defaultPath: '/home', -// routeRef: rootRouteRef, -// loader: () => -// import('./components/').then(m => -// compatWrapper( -// , -// ), -// }, -// }); - -/** - * @alpha - * This will not be the way to export extensions eventually, - * will be something like `homePlugin.extensions.homePage` or `homePlugin.extensions.get('page')` - * Just haven't worked out a nice way to fix the types yet - */ -export const extensions = { homePage }; - /** * @alpha */ From 3ac12d718954a906c1d07f66d7d894cd3978059c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 31 Jul 2024 17:02:53 +0200 Subject: [PATCH 085/393] chore: some more tests and comments Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 30 ++++++++++++++++++- .../src/wiring/createExtension.ts | 9 ++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 7f27df4947..e38a24cebe 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -599,7 +599,9 @@ describe('createExtension', () => { attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], inputs: { - test: createExtensionInput([stringDataRef], { singleton: true }), + test: createExtensionInput([stringDataRef], { + singleton: true, + }), }, config: { schema: { @@ -641,5 +643,31 @@ describe('createExtension', () => { expect(true).toBe(true); }); + + it('should allow overriding the factory function and calling the original factory', () => { + const testExtension = createExtension({ + namespace: 'test', + attachTo: { id: 'root', input: 'blob' }, + output: [stringDataRef], + config: { + schema: { + foo: z => z.string().optional(), + }, + }, + factory() { + return [stringDataRef('default')]; + }, + }); + + testExtension.override({ + factory(originalFactory) { + const response = originalFactory(); + + const foo: string = response.get(stringDataRef); + + return [stringDataRef(`foo-${foo}-override`)]; + }, + }); + }); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 3ec674f75f..678ce4e042 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -274,6 +274,8 @@ export type OverrideExtensionOptions< [key in keyof TConfigSchema]: z.infer>; }; inputs?: Expand>; + // todo(blam): Think this is better as a DataContainer instead + // should probably update that everywhere. }) => Iterable, context: { node: AppNode; @@ -292,8 +294,11 @@ export type OverrideExtensionOptions< inputs: Expand>; }, ): Iterable; - // todo(blam): need to verify that the outputs are meged properly. -} & VerifyExtensionFactoryOutput; + // todo(blam): need to verify that the outputs are merged and verified properly. +} & VerifyExtensionFactoryOutput< + UOutput & UFactoryOverrideOutput, + UFactoryOverrideOutput +>; /** @public */ export type OverridableExtension< From 45eb5b437a690b6a0d33fea3174d53ed02f88380 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 09:41:40 +0200 Subject: [PATCH 086/393] chore: got the data container working nicely Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 3 ++ .../src/wiring/createExtension.ts | 28 +++---------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index e38a24cebe..c688f92004 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -665,6 +665,9 @@ describe('createExtension', () => { const foo: string = response.get(stringDataRef); + // @ts-expect-error - fails because original factory does not return number + const number: boolean = response.get(numberDataRef); + return [stringDataRef(`foo-${foo}-override`)]; }, }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 678ce4e042..87a4342df7 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -20,7 +20,6 @@ import { Expand } from '../types'; import { AnyExtensionDataRef, ExtensionDataRef, - ExtensionDataRefToValue, ExtensionDataValue, } from './createExtensionDataRef'; import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput'; @@ -246,7 +245,6 @@ export type OverrideExtensionOptions< TConfig, TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, - UOriginalFactoryOutput extends ExtensionDataValue, TConfigSchemaOverrides extends { [key: string]: (zImpl: typeof z) => z.ZodType; }, @@ -274,9 +272,7 @@ export type OverrideExtensionOptions< [key in keyof TConfigSchema]: z.infer>; }; inputs?: Expand>; - // todo(blam): Think this is better as a DataContainer instead - // should probably update that everywhere. - }) => Iterable, + }) => ExtensionDataContainer, context: { node: AppNode; config: TConfig & @@ -296,7 +292,7 @@ export type OverrideExtensionOptions< ): Iterable; // todo(blam): need to verify that the outputs are merged and verified properly. } & VerifyExtensionFactoryOutput< - UOutput & UFactoryOverrideOutput, + UOutput & UOutputOverrides, UFactoryOverrideOutput >; @@ -312,7 +308,6 @@ export type OverridableExtension< TConfig, TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, - UFactoryOutput extends ExtensionDataValue, > = { override< TConfigSchemaOverrides extends { @@ -327,7 +322,6 @@ export type OverridableExtension< TConfig, TConfigInput, TConfigSchema, - UFactoryOutput, TConfigSchemaOverrides, UOutputOverrides, UFactoryOverrideOutput @@ -431,14 +425,7 @@ export function createExtension< }> >) > & - OverridableExtension< - UOutput, - TInputs, - TConfig, - TConfigInput, - TConfigSchema, - UFactoryOutput - >; + OverridableExtension; /** * @public * @deprecated - use the array format of `output` instead, see TODO-doc-link @@ -519,14 +506,7 @@ export function createExtension< }> >) > & - OverridableExtension< - UOutput, - TInputs, - TConfig, - TConfigInput, - TConfigSchema, - UFactoryOutput - > { + OverridableExtension { const newConfigSchema = options.config?.schema; if (newConfigSchema && options.configSchema) { throw new Error(`Cannot provide both configSchema and config.schema`); From 8d631773468d3d852335d918d7f43599be1d2a56 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 11:07:38 +0200 Subject: [PATCH 087/393] chore: added some tests Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 37 ++++++++ .../src/wiring/createExtension.ts | 86 ++++++++++++++++++- .../wiring/createExtensionOverrides.test.ts | 3 + 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index c688f92004..432fb7f89e 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { createExtensionTester } from '@backstage/frontend-test-utils'; import { coreExtensionData } from './coreExtensionData'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; @@ -671,6 +672,42 @@ describe('createExtension', () => { return [stringDataRef(`foo-${foo}-override`)]; }, }); + + expect(true).toBe(true); + }); + + it('should work functionally with overrides', () => { + const testExtension = createExtension({ + kind: 'thing', + namespace: 'test', + attachTo: { id: 'root', input: 'default' }, + output: [stringDataRef], + config: { + schema: { + foo: z => z.string().default('boom'), + }, + }, + factory({ config }) { + return [stringDataRef(config.foo)]; + }, + }); + + const overriden = testExtension.override({ + config: { + schema: { + bar: z => z.string().optional(), + }, + }, + factory(originalFactory, { config }) { + const response = originalFactory(); + + const foo: string = response.get(stringDataRef); + + return [stringDataRef(`foo-${foo}-override-${config.bar}`)]; + }, + }); + + const tester = createExtensionTester(overriden).render(); }); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 87a4342df7..200d3d1133 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { over } from 'lodash'; import { AppNode } from '../apis'; import { PortableSchema, createSchemaFromZod } from '../schema'; import { Expand } from '../types'; @@ -547,8 +548,89 @@ export function createExtension< parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); return `ExtensionDefinition{${parts.join(',')}}`; }, - override() { - // todo(blam): implement. + override< + TConfigSchemaOverrides extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UOutputOverrides extends AnyExtensionDataRef, + UFactoryOverrideOutput extends ExtensionDataValue, + >( + overrideOptions: OverrideExtensionOptions< + UOutput, + TInputs, + TConfig, + TConfigInput, + TConfigSchema, + TConfigSchemaOverrides, + UOutputOverrides, + UFactoryOverrideOutput + >, + ) { + const overrideNewConfigSchema = overrideOptions.config?.schema; + if (overrideNewConfigSchema && overrideOptions.configSchema) { + throw new Error(`Cannot provide both configSchema and config.schema`); + } + + const mergedConfigSchema = { + ...options.config?.schema, + ...overrideOptions.config?.schema, + }; + + const overrideConfigSchema = mergedConfigSchema + ? createSchemaFromZod(innerZ => + innerZ.object( + Object.fromEntries( + Object.entries(mergedConfigSchema).map(([k, v]) => [ + k, + v(innerZ), + ]), + ), + ), + ) + : overrideOptions.configSchema; + + const buildDataContainer = (outputs): ExtensionDataContainer => { + const dataMap = new Map(); + if (Symbol.iterator in outputs) { + for (const output of outputs) { + dataMap.set(output.id, output.value); + } + } + + return { + get(ref) { + return dataMap.get(ref.id); + }, + } as ExtensionDataContainer; + }; + + return createExtension({ + attachTo: options.attachTo, + output: overrideOptions.output ?? options.output, + configSchema: overrideConfigSchema, + factory: ({ node, config, inputs }) => { + if (overrideOptions.factory) { + return overrideOptions.factory( + innerCtx => { + const originalFactoryResponse = options.factory({ + node, + config: innerCtx?.config ?? config, + inputs: innerCtx?.inputs ?? inputs, + }); + + return buildDataContainer(originalFactoryResponse); + }, + { node, config, inputs }, + ); + } + return options.factory(originalFactory, context); + }, + disabled: overrideOptions.disabled ?? options.disabled, + inputs: overrideOptions.inputs ?? options.inputs, + kind: overrideOptions.kind ?? options.kind, + name: overrideOptions.name ?? options.name, + namespace: overrideOptions.namespace ?? options.namespace, + }); }, } as InternalExtensionDefinition< TConfig & diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index aa00247222..555cd82d4c 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -75,6 +75,7 @@ describe('createExtensionOverrides', () => { "id": "a", "inputs": {}, "output": {}, + "override": [Function], "toString": [Function], "version": "v1", }, @@ -90,6 +91,7 @@ describe('createExtensionOverrides', () => { "id": "b", "inputs": {}, "output": {}, + "override": [Function], "toString": [Function], "version": "v1", }, @@ -105,6 +107,7 @@ describe('createExtensionOverrides', () => { "id": "k:c/n", "inputs": {}, "output": {}, + "override": [Function], "toString": [Function], "version": "v1", }, From 4f446c44f627f2081bd373727a8defef8f2113fa Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 12:06:16 +0200 Subject: [PATCH 088/393] chore: test with the extension tester Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 432fb7f89e..6aa172cc5a 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -695,7 +695,7 @@ describe('createExtension', () => { const overriden = testExtension.override({ config: { schema: { - bar: z => z.string().optional(), + bar: z => z.string().default('hello'), }, }, factory(originalFactory, { config }) { @@ -707,7 +707,15 @@ describe('createExtension', () => { }, }); - const tester = createExtensionTester(overriden).render(); + expect(createExtensionTester(overriden).data(stringDataRef)).toBe( + 'foo-boom-override-hello', + ); + + expect( + createExtensionTester(overriden, { + config: { foo: 'hello', bar: 'world' }, + }).data(stringDataRef), + ).toBe('foo-hello-override-world'); }); }); }); From 4a6d9dc85e2042f854d7999e1a08e98ddc43ad53 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 13:46:57 +0200 Subject: [PATCH 089/393] chore: reset page extension Signed-off-by: blam --- .../src/extensions/createPageExtension.tsx | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index 27b6b53b63..cd434a3ffc 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -22,7 +22,6 @@ import { createExtension, ResolvedExtensionInputs, AnyExtensionInputMap, - createExtensionBlueprint, } from '../wiring'; import { RouteRef } from '../routing'; import { Expand } from '../types'; @@ -96,59 +95,3 @@ export function createPageExtension< }, }); } - -/** - * A blueprint for creating extensions for routable React page components. - * @public - */ -export const PageExtensionBlueprint = createExtensionBlueprint({ - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - output: [ - coreExtensionData.routePath, - coreExtensionData.reactElement, - coreExtensionData.routeRef.optional(), - ], - config: { - schema: { - path: z => z.string().optional(), - }, - }, - factory( - { - defaultPath, - loader, - routeRef, - }: { - defaultPath?: string; - // TODO(blam) This type is impossible to type properly here as we don't have access - // to the input type generic. Maybe not a deal breaker though. - // It means we have to override the factory function in the `.make` method instead. - loader: (opts: unknown) => Promise; - routeRef?: RouteRef; - }, - { config, inputs, node }, - ) { - const ExtensionComponent = lazy(() => - loader({ config, inputs }).then(element => ({ default: () => element })), - ); - - // TODO(blam): this is a little awkward for optional returns. - // I wonder if we should be using generators or yield instead - // for a better API here. - const outputs = [ - coreExtensionData.routePath(config.path ?? defaultPath!), - coreExtensionData.reactElement( - - - , - ), - ]; - - if (routeRef) { - return [...outputs, coreExtensionData.routeRef(routeRef)]; - } - - return outputs; - }, -}); From 8e185e43ce5d4d99369d45358393f5d4d2ff1908 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 7 Aug 2024 15:04:20 +0200 Subject: [PATCH 090/393] Add Notifications to documentation sidebar Signed-off-by: Marek Libra --- docs/notifications/{getting-started.md => index.md} | 4 ++-- microsite/sidebars.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) rename docs/notifications/{getting-started.md => index.md} (98%) diff --git a/docs/notifications/getting-started.md b/docs/notifications/index.md similarity index 98% rename from docs/notifications/getting-started.md rename to docs/notifications/index.md index dc006e21b2..95d77a9b7f 100644 --- a/docs/notifications/getting-started.md +++ b/docs/notifications/index.md @@ -1,5 +1,5 @@ --- -id: getting-started +id: index title: Getting Started description: How to get started with the notifications and signals --- @@ -203,7 +203,7 @@ The use of signals with notifications is optional but generally enhances user ex When a notification is created, a new signal is emitted to a general-purpose message bus to announce it to subscribed listeners. -The frontend maintains a persistent connection (web socket) to receive these announcements from the notifications channel. +The frontend maintains a persistent connection (WebSocket) to receive these announcements from the notifications channel. The specific details of the updated or created notification should be retrieved via a request to the notifications API, except for new notifications, where the payload is included in the signal for performance reasons. In a frontend plugin, to subscribe for notifications' signals: diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 075025afbb..71d0257dc7 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -295,6 +295,7 @@ "conf/writing", "conf/defining" ], + "Notifications": ["notifications/index"], "Auth and identity": [ "auth/index", { From d0e441e1b88c22b04f92270d9ceb6235ef920085 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 7 Aug 2024 09:30:12 -0500 Subject: [PATCH 091/393] Added missing `@internal` scope to plugin modules doc Signed-off-by: Andre Wanlin --- docs/backend-system/architecture/06-modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/06-modules.md b/docs/backend-system/architecture/06-modules.md index eb00e737e3..010f36109c 100644 --- a/docs/backend-system/architecture/06-modules.md +++ b/docs/backend-system/architecture/06-modules.md @@ -52,7 +52,7 @@ This allows you to install the module in your backend instance by just referenci ```ts backend.add( - import('backstage-plugin-catalog-backend-module-example-processor'), + import('@internal/backstage-plugin-catalog-backend-module-example-processor'), ); ``` From 9fdbbd0889ec6af08e21b4dc7092cedbe7fac3bf Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 17:11:24 +0200 Subject: [PATCH 092/393] chore: more work towards supporting extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Signed-off-by: blam --- .../src/extensions/createApiExtension.test.ts | 2 + .../createAppRootElementExtension.test.tsx | 2 + .../createAppRootWrapperExtension.test.tsx | 2 + .../createNavLogoExtension.test.tsx | 1 + .../extensions/createPageExtension.test.tsx | 3 + .../extensions/createRouterExtension.test.tsx | 2 + .../createTranslationExtension.test.ts | 3 + .../src/wiring/createExtension.test.ts | 28 +- .../src/wiring/createExtension.ts | 467 +++++++++--------- .../wiring/createExtensionBlueprint.test.tsx | 3 + .../src/wiring/createExtensionBlueprint.ts | 17 +- 11 files changed, 250 insertions(+), 280 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts index c4d0239094..8f8a0cd9ac 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts @@ -48,6 +48,7 @@ describe('createApiExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); }); @@ -85,6 +86,7 @@ describe('createApiExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx index c8bf6ee355..6e80f6fe13 100644 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx @@ -41,6 +41,7 @@ describe('createAppRootElementExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); createExtensionTester(extension).render(); @@ -88,6 +89,7 @@ describe('createAppRootElementExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); createExtensionTester(extension, { config: { name: 'Robin' } }) diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx index d21cfa7583..993c4fdee3 100644 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx @@ -42,6 +42,7 @@ describe('createAppRootWrapperExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); createExtensionTester( @@ -94,6 +95,7 @@ describe('createAppRootWrapperExtension', () => { component: expect.anything(), }, factory: expect.any(Function), + override: expect.any(Function), toString: expect.any(Function), }); diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx index 8c54743f1d..17187a339d 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx @@ -41,6 +41,7 @@ describe('createNavLogoExtension', () => { logos: expect.anything(), }, factory: expect.any(Function), + override: expect.any(Function), toString: expect.any(Function), }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx index 374400fce8..e5dd4027c0 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx @@ -56,6 +56,7 @@ describe('createPageExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); expect( @@ -78,6 +79,7 @@ describe('createPageExtension', () => { kind: 'page', attachTo: { id: 'other', input: 'place' }, configSchema: expect.anything(), + override: expect.any(Function), disabled: true, inputs: { first: createExtensionInput({ @@ -115,6 +117,7 @@ describe('createPageExtension', () => { }, factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx index 7109e1a8aa..2c16076fcf 100644 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx @@ -50,6 +50,7 @@ describe('createRouterExtension', () => { component: expect.anything(), }, factory: expect.any(Function), + override: expect.any(Function), toString: expect.any(Function), }); @@ -119,6 +120,7 @@ describe('createRouterExtension', () => { component: expect.anything(), }, factory: expect.any(Function), + override: expect.any(Function), toString: expect.any(Function), }); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts index 975901b8e5..019c565cbd 100644 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts @@ -46,6 +46,7 @@ describe('createTranslationExtension', () => { namespace: 'test', attachTo: { id: 'app', input: 'translations' }, disabled: false, + override: expect.any(Function), inputs: {}, output: { resource: createTranslationExtension.translationDataRef, @@ -84,6 +85,7 @@ describe('createTranslationExtension', () => { namespace: 'test', attachTo: { id: 'app', input: 'translations' }, disabled: false, + override: expect.any(Function), inputs: {}, output: { resource: createTranslationExtension.translationDataRef, @@ -120,6 +122,7 @@ describe('createTranslationExtension', () => { version: 'v1', kind: 'translation', namespace: 'test', + override: expect.any(Function), name: 'sv', attachTo: { id: 'app', input: 'translations' }, disabled: false, diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 6aa172cc5a..30375049bf 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -15,7 +15,6 @@ */ import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { coreExtensionData } from './coreExtensionData'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; @@ -306,9 +305,7 @@ describe('createExtension', () => { baz: z => z.string().optional(), }, }, - output: { - foo: stringDataRef, - }, + output: [stringDataRef], factory({ config }) { const a1: string = config.foo; const a2: string = config.bar; @@ -322,12 +319,10 @@ describe('createExtension', () => { const c3: number = config.baz; unused(a1, a2, a3, c1, c2, c3); - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); expect(String(extension)).toBe( 'ExtensionDefinition{namespace=test,attachTo=root@default}', ); @@ -614,18 +609,7 @@ describe('createExtension', () => { }, }); - const override = testExtension.override({ - output: [numberDataRef], - factory(_, { inputs }) { - return [ - numberDataRef(inputs.test.get(stringDataRef).length), - stringDataRef('default'), - ]; - }, - }); - - // @ts-expect-error - should fail as the string output from previous is not provided - const override2 = testExtension.override({ + const override1 = testExtension.override({ output: [numberDataRef], factory(_, { inputs }) { return [numberDataRef(inputs.test.get(stringDataRef).length)]; @@ -633,14 +617,14 @@ describe('createExtension', () => { }); // @ts-expect-error - this should fail because string output should be merged? - const override3 = testExtension.override({ + const override2 = testExtension.override({ output: [numberDataRef], factory(_, { inputs }) { return [stringDataRef(inputs.test.get(stringDataRef))]; }, }); - unused(override, override2, override3); + unused(override1, override2); expect(true).toBe(true); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 200d3d1133..6e75fc76e5 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { over } from 'lodash'; import { AppNode } from '../apis'; import { PortableSchema, createSchemaFromZod } from '../schema'; import { Expand } from '../types'; +import { createDataContainer } from './createExtensionBlueprint'; import { AnyExtensionDataRef, ExtensionDataRef, @@ -128,7 +128,6 @@ export interface LegacyCreateExtensionOptions< TInputs extends AnyExtensionInputMap, TConfig, TConfigInput, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, > { kind?: string; namespace?: string; @@ -137,21 +136,10 @@ export interface LegacyCreateExtensionOptions< disabled?: boolean; inputs?: TInputs; output: TOutput; - /** @deprecated - use `config.schema` instead */ configSchema?: PortableSchema; - config?: { - schema: TConfigSchema; - }; factory(context: { node: AppNode; - config: TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }); + config: TConfig; inputs: Expand>; }): Expand>; } @@ -193,8 +181,6 @@ export type CreateExtensionOptions< { optional: boolean; singleton: boolean } >; }, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, > = { @@ -205,27 +191,30 @@ export type CreateExtensionOptions< disabled?: boolean; inputs?: TInputs; output: Array; - /** @deprecated - use `config.schema` instead */ - configSchema?: PortableSchema; config?: { schema: TConfigSchema; }; factory(context: { node: AppNode; - config: TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }); + config: { + [key in keyof TConfigSchema]: z.infer>; + }; inputs: Expand>; }): Iterable; } & VerifyExtensionFactoryOutput; /** @public */ -export interface ExtensionDefinition { +export interface ExtensionDefinition< + TConfig, + TConfigInput = TConfig, + UOutput extends AnyExtensionDataRef = AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + } = {}, +> { $$type: '@backstage/ExtensionDefinition'; readonly kind?: string; readonly namespace?: string; @@ -233,102 +222,71 @@ export interface ExtensionDefinition { readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; -} -export type OverrideExtensionOptions< - UOutput extends AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, - TConfig, - TConfigInput, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, - TConfigSchemaOverrides extends { - [key: string]: (zImpl: typeof z) => z.ZodType; - }, - UOutputOverrides extends AnyExtensionDataRef, - UFactoryOverrideOutput extends ExtensionDataValue, -> = { - kind?: string; - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - output?: Array; - /** @deprecated - use `config.schema` instead */ - configSchema?: PortableSchema; - config?: { - schema: TConfigSchemaOverrides & { - [KName in keyof TConfig]?: `Error: Config key '${KName & - string}' is already defined in parent schema`; - }; - }; - factory?( - originalFactory: (context?: { - config?: { - [key in keyof TConfigSchema]: z.infer>; - }; - inputs?: Expand>; - }) => ExtensionDataContainer, - context: { - node: AppNode; - config: TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchemaOverrides]: z.infer< - ReturnType - >; - } & { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }); - inputs: Expand>; - }, - ): Iterable; - // todo(blam): need to verify that the outputs are merged and verified properly. -} & VerifyExtensionFactoryOutput< - UOutput & UOutputOverrides, - UFactoryOverrideOutput ->; - -/** @public */ -export type OverridableExtension< - UOutput extends AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, - TConfig, - TConfigInput, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, -> = { override< - TConfigSchemaOverrides extends { + TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, - UOutputOverrides extends AnyExtensionDataRef, - UFactoryOverrideOutput extends ExtensionDataValue, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, >( - options: OverrideExtensionOptions< - UOutput, - TInputs, - TConfig, - TConfigInput, - TConfigSchema, - TConfigSchemaOverrides, - UOutputOverrides, - UFactoryOverrideOutput + args: { + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory( + originalFactory: (context?: { + config?: TConfig; + inputs?: Expand>; + }) => ExtensionDataContainer, + context: { + node: AppNode; + config: TConfig & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + UFactoryOutput >, - ): ExtensionDefinition; -}; + ): ExtensionDefinition< + { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + } & TConfig, + z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + > & + TConfigInput, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + TInputs & TExtraInputs + >; +} /** @internal */ export type InternalExtensionDefinition = @@ -397,36 +355,27 @@ export function createExtension< { optional: boolean; singleton: boolean } >; }, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, >( options: CreateExtensionOptions< UOutput, TInputs, - TConfig, - TConfigInput, TConfigSchema, UFactoryOutput >, ): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >) -> & - OverridableExtension; + { + [key in keyof TConfigSchema]: z.infer>; + }, + z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >, + UOutput, + TInputs +>; /** * @public * @deprecated - use the array format of `output` instead, see TODO-doc-link @@ -436,31 +385,14 @@ export function createExtension< TInputs extends AnyExtensionInputMap, TConfig, TConfigInput, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, >( options: LegacyCreateExtensionOptions< TOutput, TInputs, TConfig, - TConfigInput, - TConfigSchema + TConfigInput >, -): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >) ->; +): ExtensionDefinition; export function createExtension< UOutput extends AnyExtensionDataRef, TInputs extends { @@ -476,20 +408,12 @@ export function createExtension< UFactoryOutput extends ExtensionDataValue, >( options: - | CreateExtensionOptions< - UOutput, - TInputs, - TConfig, - TConfigInput, - TConfigSchema, - UFactoryOutput - > + | CreateExtensionOptions | LegacyCreateExtensionOptions< AnyExtensionDataMap, TLegacyInputs, TConfig, - TConfigInput, - TConfigSchema + TConfigInput >, ): ExtensionDefinition< TConfig & @@ -505,22 +429,29 @@ export function createExtension< z.ZodObject<{ [key in keyof TConfigSchema]: ReturnType; }> - >) -> & - OverridableExtension { - const newConfigSchema = options.config?.schema; - if (newConfigSchema && options.configSchema) { + >), + UOutput, + TInputs +> { + if ('configSchema' in options && 'config' in options) { throw new Error(`Cannot provide both configSchema and config.schema`); } - const configSchema = newConfigSchema - ? createSchemaFromZod(innerZ => + let configSchema: PortableSchema | undefined; + if ('configSchema' in options) { + configSchema = options.configSchema; + } + if ('config' in options) { + const newConfigSchema = options.config?.schema; + configSchema = + newConfigSchema && + createSchemaFromZod(innerZ => innerZ.object( Object.fromEntries( Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]), ), ), - ) - : options.configSchema; + ); + } return { $$type: '@backstage/ExtensionDefinition', @@ -548,89 +479,133 @@ export function createExtension< parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); return `ExtensionDefinition{${parts.join(',')}}`; }, - override< - TConfigSchemaOverrides extends { + override: < + TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, - UOutputOverrides extends AnyExtensionDataRef, - UFactoryOverrideOutput extends ExtensionDataValue, - >( - overrideOptions: OverrideExtensionOptions< + UOverrideFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + >(overrideOptions: { + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TExtraInputs; + output?: Array; + config?: { + schema: TExtensionConfigSchema; + }; + factory( + originalFactory: (context?: { + config?: { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; + inputs?: Expand>; + }) => ExtensionDataContainer, + context: { + node: AppNode; + config: { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + } & { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable; + }): ExtensionDefinition< + { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + } & { + [key in keyof TConfigSchema]: z.infer>; + }, + z.input< + z.ZodObject< + { + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: ReturnType; + } + > + >, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + TInputs & TExtraInputs + > => { + if (!Array.isArray(options.output)) { + throw new Error( + 'Cannot override an extension that is not declared using the new format with outputs as an array', + ); + } + const newOptions = options as CreateExtensionOptions< UOutput, TInputs, - TConfig, - TConfigInput, TConfigSchema, - TConfigSchemaOverrides, - UOutputOverrides, - UFactoryOverrideOutput - >, - ) { + UFactoryOutput + >; const overrideNewConfigSchema = overrideOptions.config?.schema; - if (overrideNewConfigSchema && overrideOptions.configSchema) { - throw new Error(`Cannot provide both configSchema and config.schema`); - } - const mergedConfigSchema = { - ...options.config?.schema, - ...overrideOptions.config?.schema, - }; - - const overrideConfigSchema = mergedConfigSchema - ? createSchemaFromZod(innerZ => - innerZ.object( - Object.fromEntries( - Object.entries(mergedConfigSchema).map(([k, v]) => [ - k, - v(innerZ), - ]), - ), - ), - ) - : overrideOptions.configSchema; - - const buildDataContainer = (outputs): ExtensionDataContainer => { - const dataMap = new Map(); - if (Symbol.iterator in outputs) { - for (const output of outputs) { - dataMap.set(output.id, output.value); - } - } - - return { - get(ref) { - return dataMap.get(ref.id); - }, - } as ExtensionDataContainer; - }; + const schema = { + ...newOptions.config?.schema, + ...overrideNewConfigSchema, + } as TConfigSchema & TExtensionConfigSchema; return createExtension({ - attachTo: options.attachTo, - output: overrideOptions.output ?? options.output, - configSchema: overrideConfigSchema, + kind: newOptions.kind, + namespace: newOptions.namespace, + name: newOptions.name, + attachTo: overrideOptions.attachTo ?? newOptions.attachTo, + disabled: overrideOptions.disabled ?? newOptions.disabled, + inputs: { ...overrideOptions.inputs, ...newOptions.inputs }, + output: overrideOptions.output ?? newOptions.output, + config: Object.keys(schema).length === 0 ? undefined : { schema }, factory: ({ node, config, inputs }) => { - if (overrideOptions.factory) { - return overrideOptions.factory( - innerCtx => { - const originalFactoryResponse = options.factory({ - node, - config: innerCtx?.config ?? config, - inputs: innerCtx?.inputs ?? inputs, - }); - - return buildDataContainer(originalFactoryResponse); - }, - { node, config, inputs }, - ); + if (!overrideOptions.factory) { + return newOptions.factory({ + node, + config, + inputs: inputs as unknown as Expand< + ResolvedExtensionInputs + >, + }); } - return options.factory(originalFactory, context); + return overrideOptions.factory( + (innerContext?: { + config?: { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; + inputs?: Expand>; + }): ExtensionDataContainer => { + return createDataContainer( + newOptions.factory({ + node, + config: innerContext?.config ?? config, + inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + }) as Iterable, + ); + }, + { + node, + config, + inputs, + }, + ); }, - disabled: overrideOptions.disabled ?? options.disabled, - inputs: overrideOptions.inputs ?? options.inputs, - kind: overrideOptions.kind ?? options.kind, - name: overrideOptions.name ?? options.name, - namespace: overrideOptions.namespace ?? options.namespace, - }); + } as CreateExtensionOptions); }, } as InternalExtensionDefinition< TConfig & diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 66c3939a96..924eba2cba 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -69,6 +69,7 @@ describe('createExtensionBlueprint', () => { output: [coreExtensionData.reactElement], factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), version: 'v2', }); @@ -108,6 +109,7 @@ describe('createExtensionBlueprint', () => { output: [coreExtensionData.reactElement], factory: expect.any(Function), toString: expect.any(Function), + override: expect.any(Function), version: 'v2', }); @@ -389,6 +391,7 @@ describe('createExtensionBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 88b2523111..a8bf42c7f8 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -171,7 +171,7 @@ export interface ExtensionBlueprint< } /** @internal */ -function createDataContainer( +export function createDataContainer( values: Iterable< UData extends ExtensionDataRef ? ExtensionDataValue @@ -241,10 +241,7 @@ class ExtensionBlueprintImpl< name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; - inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & - string}' is already defined in parent definition`; - }; + inputs?: TExtraInputs; output?: Array; params?: TParams; config?: { @@ -363,9 +360,7 @@ class ExtensionBlueprintImpl< } throw new Error('Either params or factory must be provided'); }, - } as CreateExtensionOptions< - UOutput, - TInputs & TExtraInputs, + } as CreateExtensionOptions) as ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType @@ -383,10 +378,8 @@ class ExtensionBlueprintImpl< [key in keyof TConfigSchema]: ReturnType; } > - >, - TConfigSchema, - UFactoryOutput - >); + > + >; } } From feb5fc73c386997e014714c22f6ceacbaa3d2b01 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 17:30:47 +0200 Subject: [PATCH 093/393] chore: fix tests and update api-reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Signed-off-by: blam --- .../src/tree/instantiateAppNodeTree.test.ts | 14 +- .../src/tree/resolveAppNodeSpecs.test.ts | 1 + packages/frontend-plugin-api/api-report.md | 180 +++++++++++------- .../wiring/createExtensionOverrides.test.ts | 3 - .../wiring/resolveExtensionDefinition.test.ts | 2 + .../src/wiring/resolveExtensionDefinition.ts | 8 +- .../src/app/createExtensionTester.tsx | 2 +- plugins/catalog-react/api-report-alpha.md | 6 +- plugins/catalog/api-report-alpha.md | 2 +- plugins/search-react/api-report-alpha.md | 2 +- plugins/search/api-report-alpha.md | 11 +- plugins/techdocs/api-report-alpha.md | 4 +- plugins/user-settings/api-report-alpha.md | 4 +- 13 files changed, 146 insertions(+), 93 deletions(-) diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index de8889daf0..aded5d7826 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -68,7 +68,7 @@ function makeNode( function makeInstanceWithId( extension: Extension, - config?: TConfig, + config?: TConfigInput, ): AppNode { const node = makeNode(extension, { config }); return { @@ -640,12 +640,12 @@ describe('instantiateAppNodeTree', () => { name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef, otherDataRef.optional()], - configSchema: createSchemaFromZod(z => - z.object({ - output: z.string().default('test'), - other: z.number().optional(), - }), - ), + config: { + schema: { + output: z => z.string().default('test'), + other: z => z.number().optional(), + }, + }, factory({ config }) { return [ testDataRef(config.output), diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 8af2aef070..65f3f77640 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -48,6 +48,7 @@ function makeExtDef( name, attachTo: { id: attachId, input: 'default' }, disabled: status === 'disabled', + override: () => ({} as ExtensionDefinition), } as ExtensionDefinition; } diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 4dc9c99c78..325dbd193e 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -396,7 +396,7 @@ export function createApiExtension< configSchema?: PortableSchema; inputs?: TInputs; }, -): ExtensionDefinition; +): ExtensionDefinition; // @public (undocumented) export namespace createApiExtension { @@ -492,7 +492,7 @@ export function createComponentExtension< inputs: Expand>; }) => ComponentType; }; -}): ExtensionDefinition; +}): ExtensionDefinition; // @public (undocumented) export namespace createComponentExtension { @@ -524,8 +524,6 @@ export function createExtension< } >; }, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType; }, @@ -534,26 +532,20 @@ export function createExtension< options: CreateExtensionOptions< UOutput, TInputs, - TConfig, - TConfigInput, TConfigSchema, UFactoryOutput >, ): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >) + { + [key in keyof TConfigSchema]: z.infer>; + }, + z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >, + UOutput, + TInputs >; // @public @deprecated (undocumented) @@ -562,33 +554,14 @@ export function createExtension< TInputs extends AnyExtensionInputMap, TConfig, TConfigInput, - TConfigSchema extends { - [key: string]: (zImpl: typeof z) => z.ZodType; - }, >( options: LegacyCreateExtensionOptions< TOutput, TInputs, TConfig, - TConfigInput, - TConfigSchema + TConfigInput >, -): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >) ->; +): ExtensionDefinition; // @public export function createExtensionBlueprint< @@ -751,8 +724,6 @@ export type CreateExtensionOptions< } >; }, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType; }, @@ -768,20 +739,14 @@ export type CreateExtensionOptions< disabled?: boolean; inputs?: TInputs; output: Array; - configSchema?: PortableSchema; config?: { schema: TConfigSchema; }; factory(context: { node: AppNode; - config: TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }); + config: { + [key in keyof TConfigSchema]: z.infer>; + }; inputs: Expand>; }): Iterable; } & VerifyExtensionFactoryOutput; @@ -827,7 +792,9 @@ export function createNavItemExtension(options: { }, { title?: string | undefined; - } + }, + never, + never >; // @public (undocumented) @@ -850,7 +817,7 @@ export function createNavLogoExtension(options: { namespace?: string; logoIcon: JSX.Element; logoFull: JSX.Element; -}): ExtensionDefinition<{}, {}>; +}): ExtensionDefinition; // @public (undocumented) export namespace createNavLogoExtension { @@ -1005,7 +972,7 @@ export function createSubRouteRef< // @public (undocumented) export function createThemeExtension( theme: AppTheme, -): ExtensionDefinition<{}, {}>; +): ExtensionDefinition; // @public (undocumented) export namespace createThemeExtension { @@ -1021,7 +988,7 @@ export namespace createThemeExtension { export function createTranslationExtension(options: { name?: string; resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition<{}, {}>; +}): ExtensionDefinition; // @public (undocumented) export namespace createTranslationExtension { @@ -1259,7 +1226,20 @@ export type ExtensionDataValues = { }; // @public (undocumented) -export interface ExtensionDefinition { +export interface ExtensionDefinition< + TConfig, + TConfigInput = TConfig, + UOutput extends AnyExtensionDataRef = AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + } = {}, +> { // (undocumented) $$type: '@backstage/ExtensionDefinition'; // (undocumented) @@ -1277,6 +1257,76 @@ export interface ExtensionDefinition { readonly name?: string; // (undocumented) readonly namespace?: string; + // (undocumented) + override< + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + >( + args: { + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory( + originalFactory: (context?: { + config?: TConfig; + inputs?: Expand>; + }) => ExtensionDataContainer, + context: { + node: AppNode; + config: TConfig & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + UFactoryOutput + >, + ): ExtensionDefinition< + { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + } & TConfig, + z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + > & + TConfigInput, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + TInputs & TExtraInputs + >; } // @public (undocumented) @@ -1418,9 +1468,6 @@ export interface LegacyCreateExtensionOptions< TInputs extends AnyExtensionInputMap, TConfig, TConfigInput, - TConfigSchema extends { - [key: string]: (zImpl: typeof z) => z.ZodType; - }, > { // (undocumented) attachTo: { @@ -1428,24 +1475,13 @@ export interface LegacyCreateExtensionOptions< input: string; }; // (undocumented) - config?: { - schema: TConfigSchema; - }; - // @deprecated (undocumented) configSchema?: PortableSchema; // (undocumented) disabled?: boolean; // (undocumented) factory(context: { node: AppNode; - config: TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }); + config: TConfig; inputs: Expand>; }): Expand>; // (undocumented) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index 555cd82d4c..aa00247222 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -75,7 +75,6 @@ describe('createExtensionOverrides', () => { "id": "a", "inputs": {}, "output": {}, - "override": [Function], "toString": [Function], "version": "v1", }, @@ -91,7 +90,6 @@ describe('createExtensionOverrides', () => { "id": "b", "inputs": {}, "output": {}, - "override": [Function], "toString": [Function], "version": "v1", }, @@ -107,7 +105,6 @@ describe('createExtensionOverrides', () => { "id": "k:c/n", "inputs": {}, "output": {}, - "override": [Function], "toString": [Function], "version": "v1", }, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index e1bfcbfc11..74d081e95d 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -23,6 +23,7 @@ describe('resolveExtensionDefinition', () => { version: 'v2', attachTo: { id: '', input: '' }, disabled: false, + override: () => ({} as ExtensionDefinition), }; it.each([ @@ -63,6 +64,7 @@ describe('old resolveExtensionDefinition', () => { version: 'v1', attachTo: { id: '', input: '' }, disabled: false, + override: () => ({} as ExtensionDefinition), }; it.each([ diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 18b6f0a653..0c8a7959f7 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -100,7 +100,13 @@ export function resolveExtensionDefinition( context?: { namespace?: string }, ): Extension { const internalDefinition = toInternalExtensionDefinition(definition); - const { name, kind, namespace: _, ...rest } = internalDefinition; + const { + name, + kind, + namespace: _skip1, + override: _skip2, + ...rest + } = internalDefinition; const namespace = internalDefinition.namespace ?? context?.namespace; const namePart = diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index bbe52c3fbc..ce7da3701d 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -169,7 +169,7 @@ export class ExtensionTester { : [...internal.output, coreExtensionData.routePath], factory: params => { const parentOutput = Array.from( - internal.factory(params) as Iterable< + internal.factory(params as any) as Iterable< ExtensionDataValue >, ).filter(val => val.id !== coreExtensionData.routePath.id); diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index 206c0160e2..3ff0788b04 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -115,7 +115,7 @@ export function createEntityCardExtension< config: TConfig; inputs: Expand>; }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition; // @alpha (undocumented) export function createEntityContentExtension< @@ -148,7 +148,9 @@ export function createEntityContentExtension< filter?: string | undefined; title?: string | undefined; path?: string | undefined; - } + }, + never, + never >; // @alpha diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 336bf4bc2b..726ee80573 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -108,7 +108,7 @@ export function createCatalogFilterExtension< inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition; // @alpha (undocumented) const _default: BackstagePlugin< diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index c6e011fe41..56c0b6eab9 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -25,7 +25,7 @@ export function createSearchResultListItemExtension< }, >( options: SearchResultItemExtensionOptions, -): ExtensionDefinition; +): ExtensionDefinition; // @alpha (undocumented) export namespace createSearchResultListItemExtension { diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index e9b86f1f87..bb670522e4 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -17,7 +18,7 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const searchApi: ExtensionDefinition<{}, {}>; +export const searchApi: ExtensionDefinition<{}, {}, never, never>; // @alpha (undocumented) export const searchNavItem: ExtensionDefinition< @@ -26,7 +27,9 @@ export const searchNavItem: ExtensionDefinition< }, { title?: string | undefined; - } + }, + never, + never >; // @alpha (undocumented) @@ -38,7 +41,9 @@ export const searchPage: ExtensionDefinition< { path: string; noTrack: boolean; - } + }, + AnyExtensionDataRef, + {} >; // (No @packageDocumentation comment for this package) diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index 04a0c5c512..e3c48ba214 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -37,7 +37,9 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition< asListItem: boolean; asLink: boolean; title?: string | undefined; - } + }, + never, + never >; // (No @packageDocumentation comment for this package) diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index fdb2c5f6bf..d0fad90e08 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -24,7 +24,9 @@ export const settingsNavItem: ExtensionDefinition< }, { title?: string | undefined; - } + }, + never, + never >; // @alpha (undocumented) From 467ec43f55ac9f4795bc95ba34dee1c2748b613d Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 8 Aug 2024 12:13:05 +0200 Subject: [PATCH 094/393] chore: wip Signed-off-by: blam --- .../src/wiring/createExtension.test.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 30375049bf..547ec2a160 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -660,6 +660,57 @@ describe('createExtension', () => { expect(true).toBe(true); }); + it('should allow overriding the returned values from the parent factory', () => { + const testExtension = createExtension({ + kind: 'thing', + namespace: 'test', + attachTo: { id: 'root', input: 'default' }, + output: [stringDataRef, numberDataRef], + config: { + schema: { + foo: z => z.string().default('boom'), + }, + }, + factory({ config }) { + return [stringDataRef(config.foo), numberDataRef(42)]; + }, + }); + + const overridden = testExtension.override({ + output: [numberDataRef, stringDataRef], + *factory(originalFactory) { + const output = originalFactory(); + yield* output; + + yield numberDataRef(output.get(numberDataRef) + 1); + }, + }); + + const overridden = testExtension.override({ + output: [numberDataRef, stringDataRef], + *factory(originalFactory) { + const output = originalFactory(); + yield* output.omit(numberDataRef); + + yield numberDataRef(output.get(numberDataRef) + 1); + }, + }); + + const overridden = testExtension.override({ + output: [numberDataRef, stringDataRef], + *factory(originalFactory) { + const output = originalFactory(); + yield* output; + + yield numberDataRef(output.get(numberDataRef) + 1); + }, + }); + + const tester = createExtensionTester(overridden); + + expect(tester.data(numberDataRef)).toBe(43); + }); + it('should work functionally with overrides', () => { const testExtension = createExtension({ kind: 'thing', From 2d215993f14e9018a9fd031ed69c662d9b74783e Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 8 Aug 2024 13:22:59 +0200 Subject: [PATCH 095/393] chore: added changeset Signed-off-by: blam --- .changeset/thick-squids-drive.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .changeset/thick-squids-drive.md diff --git a/.changeset/thick-squids-drive.md b/.changeset/thick-squids-drive.md new file mode 100644 index 0000000000..07654da27d --- /dev/null +++ b/.changeset/thick-squids-drive.md @@ -0,0 +1,31 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +--- + +Added support for being able to override extension definitions. + +```tsx +const TestCard = EntityCardBlueprint.make({ + ... +}); + +TestCard.override({ + attachTo: { id: 'something-else', input: 'overriden' }, + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } +}); + +``` From 8fb60cb2f133a36684dd13028b02de54cb623a49 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 8 Aug 2024 13:41:00 +0200 Subject: [PATCH 096/393] chore: some more work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Signed-off-by: blam --- .changeset/thick-squids-drive.md | 5 ++++- .../src/wiring/createExtension.test.ts | 20 ------------------- .../src/wiring/createExtension.ts | 9 ++++++++- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/.changeset/thick-squids-drive.md b/.changeset/thick-squids-drive.md index 07654da27d..0e798063c0 100644 --- a/.changeset/thick-squids-drive.md +++ b/.changeset/thick-squids-drive.md @@ -11,12 +11,15 @@ const TestCard = EntityCardBlueprint.make({ }); TestCard.override({ - attachTo: { id: 'something-else', input: 'overriden' }, + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema config: { schema: { newConfig: z => z.string().optional(), } }, + // override factory *factory(originalFactory, { inputs, config }){ const originalOutput = originalFactory(); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 547ec2a160..f9e39e98de 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -686,26 +686,6 @@ describe('createExtension', () => { }, }); - const overridden = testExtension.override({ - output: [numberDataRef, stringDataRef], - *factory(originalFactory) { - const output = originalFactory(); - yield* output.omit(numberDataRef); - - yield numberDataRef(output.get(numberDataRef) + 1); - }, - }); - - const overridden = testExtension.override({ - output: [numberDataRef, stringDataRef], - *factory(originalFactory) { - const output = originalFactory(); - yield* output; - - yield numberDataRef(output.get(numberDataRef) + 1); - }, - }); - const tester = createExtensionTester(overridden); expect(tester.data(numberDataRef)).toBe(43); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 6e75fc76e5..600b96ebe0 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -581,7 +581,7 @@ export function createExtension< >, }); } - return overrideOptions.factory( + const parentResult = overrideOptions.factory( (innerContext?: { config?: { [key in keyof TConfigSchema]: z.infer< @@ -604,6 +604,13 @@ export function createExtension< inputs, }, ); + + const deduplicatedResult = new Map(); + for (const item of parentResult) { + deduplicatedResult.set(item.id, item); + } + + return deduplicatedResult.values() as Iterable; }, } as CreateExtensionOptions); }, From 80b84f77e51bc924cb52dceb8ed8e25285d41872 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 8 Aug 2024 13:47:32 +0300 Subject: [PATCH 097/393] fix: notification reload on navigation this fixes issue when notifications are always reloaded when navigating in the portal. Signed-off-by: Heikki Hellgren --- .changeset/clean-kids-applaud.md | 5 +++++ .../NotificationsSideBarItem.tsx | 8 ++++---- plugins/notifications/src/hooks/useWebNotifications.ts | 10 ++++------ 3 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 .changeset/clean-kids-applaud.md diff --git a/.changeset/clean-kids-applaud.md b/.changeset/clean-kids-applaud.md new file mode 100644 index 0000000000..907b5978cd --- /dev/null +++ b/.changeset/clean-kids-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Fixed issue with notification reloading on page change diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 457893b016..22ce11db69 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -111,7 +111,7 @@ export const NotificationsSidebarItem = (props?: { const notificationsApi = useApi(notificationsApiRef); const alertApi = useApi(alertApiRef); const [unreadCount, setUnreadCount] = React.useState(0); - const notificationsRoute = useRouteRef(rootRouteRef); + const notificationsRoute = useRouteRef(rootRouteRef)(); // TODO: Do we want to add long polling in case signals are not available const { lastSignal } = useSignal('notifications'); const { sendWebNotification, requestUserPermission } = useWebNotifications( @@ -126,7 +126,7 @@ export const NotificationsSidebarItem = (props?: { <> { if (notification.payload.link) { notificationsApi @@ -189,7 +189,6 @@ export const NotificationsSidebarItem = (props?: { ) { return; } - notificationsApi .getNotification(signal.notification_id) .then(notification => { @@ -211,6 +210,7 @@ export const NotificationsSidebarItem = (props?: { ? `${notification.payload.title.substring(0, 50)}...` : notification.payload.title; enqueueSnackbar(snackBarText, { + key: notification.id, variant: notification.payload.severity, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, action, @@ -273,7 +273,7 @@ export const NotificationsSidebarItem = (props?: { /> )} { requestUserPermission(); }} diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index ec120a614d..cc69a0e815 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -14,18 +14,16 @@ * limitations under the License. */ import { useCallback, useState } from 'react'; -import { rootRouteRef } from '../routes'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { useNavigate } from 'react-router-dom'; import { notificationsApiRef } from '../api'; +import { rootRouteRef } from '../routes'; /** @internal */ export function useWebNotifications(enabled: boolean) { const [webNotificationPermission, setWebNotificationPermission] = useState('default'); - const notificationsRoute = useRouteRef(rootRouteRef); + const notificationsRoute = useRouteRef(rootRouteRef)(); const notificationsApi = useApi(notificationsApiRef); - const navigate = useNavigate(); const requestUserPermission = useCallback(() => { if ( @@ -64,14 +62,14 @@ export function useWebNotifications(enabled: boolean) { read: true, }); } else { - navigate(notificationsRoute()); + window.open(notificationsRoute); } notification.close(); }; return notification; }, - [webNotificationPermission, notificationsApi, navigate, notificationsRoute], + [webNotificationPermission, notificationsApi, notificationsRoute], ); return { sendWebNotification, requestUserPermission }; From 201c98bb2c09a2a226c875d9343418eacae297c2 Mon Sep 17 00:00:00 2001 From: bioerrorlog Date: Thu, 8 Aug 2024 21:02:07 +0900 Subject: [PATCH 098/393] docs: fix broken link in techdocs configuration Signed-off-by: bioerrorlog --- docs/features/techdocs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index a861a50c87..8c2348972c 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -20,7 +20,7 @@ techdocs: # spin up the techdocs-container docker image or to run mkdocs locally (assuming all the dependencies are taken care of). # You want to change this to 'local' if you are running Backstage using your own custom Docker setup and want to avoid running # into Docker in Docker situation. Read more here - # https://backstage.io/docs/features/techdocs/getting-started#disable-docker-in-docker-situation-optional + # https://backstage.io/docs/features/techdocs/getting-started/#disabling-docker-in-docker-situation-optional runIn: 'docker' From d57967cbd38c68d335099b1e288e2825a5dee4fd Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 8 Aug 2024 14:08:23 +0100 Subject: [PATCH 099/393] adds ability to configure the commit message when initializating a repo Signed-off-by: Brian Fletcher --- .changeset/five-mangos-agree.md | 5 +++++ .../src/actions/bitbucketCloud.ts | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 .changeset/five-mangos-agree.md diff --git a/.changeset/five-mangos-agree.md b/.changeset/five-mangos-agree.md new file mode 100644 index 0000000000..1ad9b6b04c --- /dev/null +++ b/.changeset/five-mangos-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Add abilty to set the initial commit message when initializing a repository using the scaffolder action. diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts index 99e8e52a43..71eacea955 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -111,6 +111,7 @@ export function createPublishBitbucketCloudAction(options: { description?: string; defaultBranch?: string; repoVisibility?: 'private' | 'public'; + gitCommitMessage?: string; sourcePath?: string; token?: string; }>({ @@ -141,6 +142,11 @@ export function createPublishBitbucketCloudAction(options: { type: 'string', description: `Sets the default branch on the repository. The default value is 'master'`, }, + gitCommitMessage: { + title: 'Git Commit Message', + type: 'string', + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }, sourcePath: { title: 'Source Path', description: @@ -178,6 +184,7 @@ export function createPublishBitbucketCloudAction(options: { repoUrl, description, defaultBranch = 'master', + gitCommitMessage, repoVisibility = 'private', } = ctx.input; @@ -256,9 +263,9 @@ export function createPublishBitbucketCloudAction(options: { auth, defaultBranch, logger: ctx.logger, - commitMessage: config.getOptionalString( - 'scaffolder.defaultCommitMessage', - ), + commitMessage: + gitCommitMessage || + config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, }); From 2c7c7901bfa4373fb7c6368538997e46ad8e4da6 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 8 Aug 2024 14:35:22 +0100 Subject: [PATCH 100/393] Adds api report changes Signed-off-by: Brian Fletcher --- plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md b/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md index a484f49f77..1f1a7ca1d0 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md @@ -36,6 +36,7 @@ export function createPublishBitbucketCloudAction(options: { description?: string | undefined; defaultBranch?: string | undefined; repoVisibility?: 'private' | 'public' | undefined; + gitCommitMessage?: string | undefined; sourcePath?: string | undefined; token?: string | undefined; }, From 2ced236e072ad432202f5ef13cd7bff987d7b8f3 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 25 Jul 2024 09:53:27 +0100 Subject: [PATCH 101/393] build(deps): bump @module-federation/enhanced to ^0.3.5 Avoids dependency on vulnerable version of ws: https://github.com/advisories/GHSA-3h5v-q93c-6h6q Signed-off-by: MT Lewis --- .changeset/slow-rocks-end.md | 5 + packages/cli/package.json | 2 +- yarn.lock | 171 ++++++++++++++++++++--------------- 3 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 .changeset/slow-rocks-end.md diff --git a/.changeset/slow-rocks-end.md b/.changeset/slow-rocks-end.md new file mode 100644 index 0000000000..f2633c9924 --- /dev/null +++ b/.changeset/slow-rocks-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `@module-federation/enhanced` to `0.3.1` diff --git a/packages/cli/package.json b/packages/cli/package.json index ba45b8f514..45f58a463c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -53,7 +53,7 @@ "@backstage/release-manifests": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@module-federation/enhanced": "^0.1.19", + "@module-federation/enhanced": "^0.3.5", "@octokit/graphql": "^5.0.0", "@octokit/graphql-schema": "^13.7.0", "@octokit/oauth-app": "^4.2.0", diff --git a/yarn.lock b/yarn.lock index c14251269c..962650fc85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3922,7 +3922,7 @@ __metadata: "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" "@manypkg/get-packages": ^1.1.3 - "@module-federation/enhanced": ^0.1.19 + "@module-federation/enhanced": ^0.3.5 "@octokit/graphql": ^5.0.0 "@octokit/graphql-schema": ^13.7.0 "@octokit/oauth-app": ^4.2.0 @@ -11132,13 +11132,22 @@ __metadata: languageName: node linkType: hard -"@module-federation/dts-plugin@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/dts-plugin@npm:0.1.21" +"@module-federation/bridge-react-webpack-plugin@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.3.5" dependencies: - "@module-federation/managers": 0.1.21 - "@module-federation/sdk": 0.1.21 - "@module-federation/third-party-dts-extractor": 0.1.21 + "@module-federation/sdk": 0.3.5 + checksum: 9fb0aab200bd63a07c1adcc71e3342c713de4806dccc2b4a8ea2eb19677b2c8753b638906ed48ab49909c753319f33792cb2afd08c0d6a80b4af742d1dc57bd1 + languageName: node + linkType: hard + +"@module-federation/dts-plugin@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/dts-plugin@npm:0.3.5" + dependencies: + "@module-federation/managers": 0.3.5 + "@module-federation/sdk": 0.3.5 + "@module-federation/third-party-dts-extractor": 0.3.5 adm-zip: ^0.5.10 ansi-colors: ^4.1.3 axios: ^1.6.7 @@ -11150,31 +11159,33 @@ __metadata: log4js: 6.9.1 node-schedule: 2.1.1 rambda: ^9.1.0 - ws: 8.17.0 + ws: 8.17.1 peerDependencies: typescript: ^4.9.0 || ^5.0.0 - vue-tsc: ^1.0.24 + vue-tsc: ">=1.0.24" peerDependenciesMeta: vue-tsc: optional: true - checksum: ec4cd030a25617698754cbac2da5463f8942cdd0a64bdc95f6ff5fd29fff6b88cf3db90e53e6b260cd3593893fe6ee2d6e149d0a46698eb7f9cc19a9df26193d + checksum: 8c8e653d0818b3df14ec399d6db0b5784f202c7909112e4884f29c4e268bd9ff6b2d891677776a65ac6204bf4df9cb2f377b7cb8884c1fee98d275d8d2f28f29 languageName: node linkType: hard -"@module-federation/enhanced@npm:^0.1.19": - version: 0.1.21 - resolution: "@module-federation/enhanced@npm:0.1.21" +"@module-federation/enhanced@npm:^0.3.5": + version: 0.3.5 + resolution: "@module-federation/enhanced@npm:0.3.5" dependencies: - "@module-federation/dts-plugin": 0.1.21 - "@module-federation/managers": 0.1.21 - "@module-federation/manifest": 0.1.21 - "@module-federation/rspack": 0.1.21 - "@module-federation/runtime-tools": 0.1.21 - "@module-federation/sdk": 0.1.21 + "@module-federation/bridge-react-webpack-plugin": 0.3.5 + "@module-federation/dts-plugin": 0.3.5 + "@module-federation/managers": 0.3.5 + "@module-federation/manifest": 0.3.5 + "@module-federation/rspack": 0.3.5 + "@module-federation/runtime-tools": 0.3.5 + "@module-federation/sdk": 0.3.5 + btoa: ^1.2.1 upath: 2.0.1 peerDependencies: typescript: ^4.9.0 || ^5.0.0 - vue-tsc: ^1.0.24 + vue-tsc: ">=1.0.24" webpack: ^5.0.0 peerDependenciesMeta: typescript: @@ -11183,91 +11194,100 @@ __metadata: optional: true webpack: optional: true - checksum: bc0ff541db1066b290b3ad9ab868437dc3d0754b9d06ab263f8fb7f54e08238eae9232a482a681dd6152e4a175578d65df9f27fe181fc8fb602d7cc1ae34807d + checksum: 40130aa86c1ae2c90cf49360754f4e9688f33900087d376215c14b64d1f596141785eb7794457bb76387bb79725a60bee1271e3911d95183e9379c6454145894 languageName: node linkType: hard -"@module-federation/managers@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/managers@npm:0.1.21" +"@module-federation/managers@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/managers@npm:0.3.5" dependencies: - "@module-federation/sdk": 0.1.21 + "@module-federation/sdk": 0.3.5 find-pkg: 2.0.0 fs-extra: 9.1.0 - checksum: 5f230d5795d86dfd68c404ee2b7a1264950c283a4b1c6f4ee9cc9579fabb413718dfbc1ff726b9c213f9d3223d944dd38dd9d04b700962e6398c3c3728d6323e + checksum: a60a21c2c9bf84fd0911365980e2f0e163485f0a952992e3fec242e8e7a33f0c803c62e1c463fb1326845f0077aef2d45d90eba0f6f69545e60e0ea708ebcaba languageName: node linkType: hard -"@module-federation/manifest@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/manifest@npm:0.1.21" +"@module-federation/manifest@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/manifest@npm:0.3.5" dependencies: - "@module-federation/dts-plugin": 0.1.21 - "@module-federation/managers": 0.1.21 - "@module-federation/sdk": 0.1.21 + "@module-federation/dts-plugin": 0.3.5 + "@module-federation/managers": 0.3.5 + "@module-federation/sdk": 0.3.5 chalk: 3.0.0 find-pkg: 2.0.0 - checksum: cef2011875f14e853a355626ae1dbc8ae3b0714d31140e329b5dd71525782b08c2e1d6ca45276a563bb3c3b7f7c4e64a31f0698ef12606f05aa6da46e759f345 + checksum: 0b0f41802630536a9a90b3e958052d2a1503bdd3927c40e1962e371ee26f2646172ef895b868fd287921a318e0bac5a513ef069517565c445fb659c150e3a257 languageName: node linkType: hard -"@module-federation/rspack@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/rspack@npm:0.1.21" +"@module-federation/rspack@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/rspack@npm:0.3.5" dependencies: - "@module-federation/dts-plugin": 0.1.21 - "@module-federation/managers": 0.1.21 - "@module-federation/manifest": 0.1.21 - "@module-federation/runtime-tools": 0.1.21 - "@module-federation/sdk": 0.1.21 - checksum: 55516285e23f4ca7127afafb14af667defbe46dc3224f85d7e07edbc8937d7fac909dfebc2f9dd73120b99bbe5135372cf0fbbe282990d80e6953a60dfa4c93e + "@module-federation/bridge-react-webpack-plugin": 0.3.5 + "@module-federation/dts-plugin": 0.3.5 + "@module-federation/managers": 0.3.5 + "@module-federation/manifest": 0.3.5 + "@module-federation/runtime-tools": 0.3.5 + "@module-federation/sdk": 0.3.5 + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: ">=1.0.24" + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + checksum: 0afd72784ebbc92ab5d295de1be938cec259f7fcdc59b753af881bbf5b7f982b816c9c04b1ee5e052326556b8a4647001a623c58562c908ee7ffe316f4171df6 languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/runtime-tools@npm:0.1.21" +"@module-federation/runtime-tools@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/runtime-tools@npm:0.3.5" dependencies: - "@module-federation/runtime": 0.1.21 - "@module-federation/webpack-bundler-runtime": 0.1.21 - checksum: 628c0c4834093520f9c71481d587c9e18163f82e481b05b1900f04e2d5da4abb69af6d814ac5cd1951057b28d73f3adeb1cee7cd83628305b10cc7988405fbc5 + "@module-federation/runtime": 0.3.5 + "@module-federation/webpack-bundler-runtime": 0.3.5 + checksum: 5287783b12fff5a37d45cb904d9e004c569bb0803680c133a19002810f7c84f8bff87594dacbc473cdc813788eb8c9feb0915fb2f446ca0f4c609d0be9e33191 languageName: node linkType: hard -"@module-federation/runtime@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/runtime@npm:0.1.21" +"@module-federation/runtime@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/runtime@npm:0.3.5" dependencies: - "@module-federation/sdk": 0.1.21 - checksum: ce4de8515b54f1cd07a3c7c4cbd35fea163294b9fb24be10827872f3ebb62cd5c289f3602efe4149d963282739f79b51947afa039ee6f36be7f66dea83d590fc + "@module-federation/sdk": 0.3.5 + checksum: 61ef8a6535e80d583d5d755d33f4edad0b6e4df6118f38296dbab5eb5f23a624c8ca7ceab8c00d6d7d4249179615cff7235f3554476933aabbc9b988321944ae languageName: node linkType: hard -"@module-federation/sdk@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/sdk@npm:0.1.21" - checksum: 6856dcfe2ef5ae939890b82010aaad911fa6c4330a05f290ae054c316c9b532d3691456a1f9e176fe05f1df2d6f2d8c7e0c842ca5648a0fd7abf270e44ed9ecb +"@module-federation/sdk@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/sdk@npm:0.3.5" + checksum: 80f720af685def16bd80d6104dd7d831df03b1b3eedb11674a95f0e4420dbbaf732d4f619fc5e06421b36549483ab31484ad8a0490ac3033ed85ac300172be7d languageName: node linkType: hard -"@module-federation/third-party-dts-extractor@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/third-party-dts-extractor@npm:0.1.21" +"@module-federation/third-party-dts-extractor@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/third-party-dts-extractor@npm:0.3.5" dependencies: find-pkg: 2.0.0 fs-extra: 9.1.0 resolve: 1.22.8 - checksum: e394fd7c2e6dbdf8df6937628680e7356ac897ee6f1309d7fbc38c00bcf4be9c0363f8bc1a75c29f7987a5a2f11f7855481813889b18e8b444ee9006aeb4a299 + checksum: f7413bee4bf77fa8576dce31580fd91a636d0db6cffa17cd5f485039c5470254410005386f4b4160298b0fced985b9d1768411a64cc96f90a0b73f2e7d1a463f languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.1.21": - version: 0.1.21 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.1.21" +"@module-federation/webpack-bundler-runtime@npm:0.3.5": + version: 0.3.5 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.3.5" dependencies: - "@module-federation/runtime": 0.1.21 - "@module-federation/sdk": 0.1.21 - checksum: 7d96002066e63bdb503964fd5fb2798be25f4135a599d87721f4d26ebe1de1affbf447c56b082f7ee850ae7798d0ac637f6a486f58591269065e114051b466e5 + "@module-federation/runtime": 0.3.5 + "@module-federation/sdk": 0.3.5 + checksum: a4ba483be2ae1a157bcbaf1e895bbbe4c6cfe3039f6403ce543aa5163add4207c69d03f98a2b2910f21fbf61086f56c1f14ff19e44b36bb201a12ddf54ee3491 languageName: node linkType: hard @@ -22372,6 +22392,15 @@ __metadata: languageName: node linkType: hard +"btoa@npm:^1.2.1": + version: 1.2.1 + resolution: "btoa@npm:1.2.1" + bin: + btoa: bin/btoa.js + checksum: afbf004fb1b1d530e053ffa66ef5bd3878b101c59d808ac947fcff96810b4452abba2b54be687adadea2ba9efc7af48b04228742789bf824ef93f103767e690c + languageName: node + linkType: hard + "buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -45058,9 +45087,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.17.0": - version: 8.17.0 - resolution: "ws@npm:8.17.0" +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -45069,7 +45098,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 147ef9eab0251364e1d2c55338ad0efb15e6913923ccbfdf20f7a8a6cb8f88432bcd7f4d8f66977135bfad35575644f9983201c1a361019594a4e53977bf6d4e + checksum: 442badcce1f1178ec87a0b5372ae2e9771e07c4929a3180321901f226127f252441e8689d765aa5cfba5f50ac60dd830954afc5aeae81609aefa11d3ddf5cecf languageName: node linkType: hard From a65cfc814916f985d6a2b3a06a8b3fea3bd6557f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 8 Aug 2024 16:19:54 +0200 Subject: [PATCH 102/393] frontend-plugin-api: add support for accessing extensions from plugin instance Signed-off-by: Patrik Oldsberg --- .changeset/old-tools-smell.md | 5 + .../app-next-example-plugin/api-report.md | 2 +- packages/frontend-plugin-api/api-report.md | 172 ++++++++++++++---- .../src/wiring/createExtension.ts | 149 ++++++++++----- .../src/wiring/createExtensionBlueprint.ts | 70 ++++--- .../src/wiring/createPlugin.test.ts | 8 +- .../src/wiring/createPlugin.ts | 67 ++++--- .../wiring/resolveExtensionDefinition.test.ts | 105 ++++++++++- .../src/wiring/resolveExtensionDefinition.ts | 30 +++ .../frontend-plugin-api/src/wiring/types.ts | 18 +- plugins/api-docs/api-report-alpha.md | 3 +- plugins/app-visualizer/api-report.md | 2 +- plugins/catalog-graph/api-report-alpha.md | 3 +- plugins/catalog-import/api-report-alpha.md | 1 + plugins/catalog-react/api-report-alpha.md | 15 +- plugins/catalog/api-report-alpha.md | 13 +- plugins/devtools/api-report-alpha.md | 1 + plugins/home/api-report-alpha.md | 2 +- plugins/kubernetes/api-report-alpha.md | 1 + plugins/org/api-report-alpha.md | 3 +- plugins/scaffolder/api-report-alpha.md | 3 +- plugins/search-react/api-report-alpha.md | 10 +- plugins/search/api-report-alpha.md | 21 ++- plugins/techdocs/api-report-alpha.md | 6 +- plugins/user-settings/api-report-alpha.md | 6 +- 25 files changed, 564 insertions(+), 152 deletions(-) create mode 100644 .changeset/old-tools-smell.md diff --git a/.changeset/old-tools-smell.md b/.changeset/old-tools-smell.md new file mode 100644 index 0000000000..4dbd65e779 --- /dev/null +++ b/.changeset/old-tools-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint. diff --git a/packages/app-next-example-plugin/api-report.md b/packages/app-next-example-plugin/api-report.md index c89a7b599b..962b1473a8 100644 --- a/packages/app-next-example-plugin/api-report.md +++ b/packages/app-next-example-plugin/api-report.md @@ -7,7 +7,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; // @public (undocumented) -const examplePlugin: BackstagePlugin<{}, {}>; +const examplePlugin: BackstagePlugin<{}, {}, {}>; export default examplePlugin; // @public (undocumented) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 325dbd193e..a8f8ce591b 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -275,17 +275,22 @@ export { BackstageIdentityResponse }; // @public (undocumented) export interface BackstagePlugin< - Routes extends AnyRoutes = AnyRoutes, - ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { + [id in string]: ExtensionDefinition; + } = {}, > { // (undocumented) readonly $$type: '@backstage/BackstagePlugin'; // (undocumented) - readonly externalRoutes: ExternalRoutes; + readonly externalRoutes: TExternalRoutes; + // (undocumented) + getExtension(id: TId): TExtensionMap[TId]; // (undocumented) readonly id: string; // (undocumented) - readonly routes: Routes; + readonly routes: TRoutes; } export { BackstageUserIdentity }; @@ -396,7 +401,15 @@ export function createApiExtension< configSchema?: PortableSchema; inputs?: TInputs; }, -): ExtensionDefinition; +): ExtensionDefinition< + TConfig, + TConfig, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @public (undocumented) export namespace createApiExtension { @@ -492,7 +505,15 @@ export function createComponentExtension< inputs: Expand>; }) => ComponentType; }; -}): ExtensionDefinition; +}): ExtensionDefinition< + TConfig, + TConfig, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @public (undocumented) export namespace createComponentExtension { @@ -528,8 +549,14 @@ export function createExtension< [key: string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, >( options: CreateExtensionOptions< + TKind, + TNamespace, + TName, UOutput, TInputs, TConfigSchema, @@ -545,7 +572,10 @@ export function createExtension< }> >, UOutput, - TInputs + TInputs, + string | undefined extends TKind ? undefined : TKind, + string | undefined extends TNamespace ? undefined : TNamespace, + string | undefined extends TName ? undefined : TName >; // @public @deprecated (undocumented) @@ -580,11 +610,17 @@ export function createExtensionBlueprint< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends string | undefined = undefined, + TName extends string | undefined = undefined, TDataRefs extends { [name in string]: AnyExtensionDataRef; } = never, >( options: CreateExtensionBlueprintOptions< + TKind, + TNamespace, + TName, TParams, UOutput, TInputs, @@ -593,6 +629,9 @@ export function createExtensionBlueprint< TDataRefs >, ): ExtensionBlueprint< + TKind, + TNamespace, + TName, TParams, UOutput, string extends keyof TInputs ? {} : TInputs, @@ -613,6 +652,9 @@ export function createExtensionBlueprint< // @public (undocumented) export type CreateExtensionBlueprintOptions< + TKind extends string, + TNamespace extends string | undefined, + TName extends string | undefined, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -632,8 +674,8 @@ export type CreateExtensionBlueprintOptions< [name in string]: AnyExtensionDataRef; }, > = { - kind: string; - namespace?: string | ((params: TParams) => string); + kind: TKind; + namespace?: TNamespace | ((params: TParams) => TNamespace); attachTo: { id: string; input: string; @@ -641,7 +683,7 @@ export type CreateExtensionBlueprintOptions< disabled?: boolean; inputs?: TInputs; output: Array; - name?: string | ((params: TParams) => string); + name?: TName | ((params: TParams) => TName); config?: { schema: TConfigSchema | ((params: TParams) => TConfigSchema); }; @@ -714,6 +756,9 @@ export function createExtensionInput< // @public (undocumented) export type CreateExtensionOptions< + TKind extends string | undefined, + TNamespace extends string | undefined, + TName extends string | undefined, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -729,9 +774,9 @@ export type CreateExtensionOptions< }, UFactoryOutput extends ExtensionDataValue, > = { - kind?: string; - namespace?: string; - name?: string; + kind?: TKind; + namespace?: TNamespace; + name?: TName; attachTo: { id: string; input: string; @@ -794,7 +839,10 @@ export function createNavItemExtension(options: { title?: string | undefined; }, never, - never + never, + string | undefined, + string | undefined, + string | undefined >; // @public (undocumented) @@ -817,7 +865,15 @@ export function createNavLogoExtension(options: { namespace?: string; logoIcon: JSX.Element; logoFull: JSX.Element; -}): ExtensionDefinition; +}): ExtensionDefinition< + unknown, + unknown, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @public (undocumented) export namespace createNavLogoExtension { @@ -865,11 +921,22 @@ export function createPageExtension< // @public (undocumented) export function createPlugin< - Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {}, + TId extends string, + TRoutes extends AnyRoutes = {}, + TExternalRoutes extends AnyExternalRoutes = {}, + TExtensions extends readonly ExtensionDefinition[] = [], >( - options: PluginOptions, -): BackstagePlugin; + options: PluginOptions, +): BackstagePlugin< + TRoutes, + TExternalRoutes, + { + [KExtension in TExtensions[number] as ResolveExtensionId< + KExtension, + TId + >]: KExtension; + } +>; // @public export function createRouteRef< @@ -972,7 +1039,15 @@ export function createSubRouteRef< // @public (undocumented) export function createThemeExtension( theme: AppTheme, -): ExtensionDefinition; +): ExtensionDefinition< + unknown, + unknown, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @public (undocumented) export namespace createThemeExtension { @@ -988,7 +1063,15 @@ export namespace createThemeExtension { export function createTranslationExtension(options: { name?: string; resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition; +}): ExtensionDefinition< + unknown, + unknown, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @public (undocumented) export namespace createTranslationExtension { @@ -1044,6 +1127,9 @@ export interface Extension { // @public (undocumented) export interface ExtensionBlueprint< + TKind extends string, + TNamespace extends string | undefined, + TName extends string | undefined, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -1068,6 +1154,8 @@ export interface ExtensionBlueprint< // (undocumented) dataRefs: TDataRefs; make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, @@ -1084,8 +1172,8 @@ export interface ExtensionBlueprint< }, >( args: { - namespace?: string; - name?: string; + namespace?: TNewNamespace; + name?: TNewName; attachTo?: { id: string; input: string; @@ -1143,7 +1231,12 @@ export interface ExtensionBlueprint< >; }> > & - TConfigInput + TConfigInput, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + TInputs & TExtraInputs, + TKind, + string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, + string | undefined extends TNewName ? TName : TNewName >; } @@ -1239,6 +1332,9 @@ export interface ExtensionDefinition< } >; } = {}, + TKind extends string | undefined = string | undefined, + TNamespace extends string | undefined = string | undefined, + TName extends string | undefined = string | undefined, > { // (undocumented) $$type: '@backstage/ExtensionDefinition'; @@ -1252,11 +1348,11 @@ export interface ExtensionDefinition< // (undocumented) readonly disabled: boolean; // (undocumented) - readonly kind?: string; + readonly kind?: TKind; // (undocumented) - readonly name?: string; + readonly name?: TName; // (undocumented) - readonly namespace?: string; + readonly namespace?: TNamespace; // (undocumented) override< TExtensionConfigSchema extends { @@ -1325,7 +1421,10 @@ export interface ExtensionDefinition< > & TConfigInput, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs + TInputs & TExtraInputs, + TKind, + TNamespace, + TName >; } @@ -1405,6 +1504,9 @@ export { googleAuthApiRef }; // @public (undocumented) export const IconBundleBlueprint: ExtensionBlueprint< + 'icon-bundle', + 'app', + undefined, { icons: { [x: string]: IconComponent; @@ -1536,19 +1638,21 @@ export { PendingOAuthRequest }; // @public (undocumented) export interface PluginOptions< - Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes, + TId extends string, + TRoutes extends AnyRoutes, + TExternalRoutes extends AnyExternalRoutes, + TExtensions extends readonly ExtensionDefinition[], > { // (undocumented) - extensions?: ExtensionDefinition[]; + extensions?: TExtensions; // (undocumented) - externalRoutes?: ExternalRoutes; + externalRoutes?: TExternalRoutes; // (undocumented) featureFlags?: FeatureFlagConfig[]; // (undocumented) - id: string; + id: TId; // (undocumented) - routes?: Routes; + routes?: TRoutes; } // @public (undocumented) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 600b96ebe0..f2d3d906ab 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -174,6 +174,9 @@ export type VerifyExtensionFactoryOutput< /** @public */ export type CreateExtensionOptions< + TKind extends string | undefined, + TNamespace extends string | undefined, + TName extends string | undefined, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -184,9 +187,9 @@ export type CreateExtensionOptions< TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, > = { - kind?: string; - namespace?: string; - name?: string; + kind?: TKind; + namespace?: TNamespace; + name?: TName; attachTo: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; @@ -214,11 +217,14 @@ export interface ExtensionDefinition< { optional: boolean; singleton: boolean } >; } = {}, + TKind extends string | undefined = string | undefined, + TNamespace extends string | undefined = string | undefined, + TName extends string | undefined = string | undefined, > { $$type: '@backstage/ExtensionDefinition'; - readonly kind?: string; - readonly namespace?: string; - readonly name?: string; + readonly kind?: TKind; + readonly namespace?: TNamespace; + readonly name?: TName; readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; @@ -284,45 +290,68 @@ export interface ExtensionDefinition< > & TConfigInput, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs + TInputs & TExtraInputs, + TKind, + TNamespace, + TName >; } /** @internal */ -export type InternalExtensionDefinition = - ExtensionDefinition & - ( - | { - readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; - factory(context: { - node: AppNode; - config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; - } - | { - readonly version: 'v2'; - readonly inputs: { +export type InternalExtensionDefinition< + TConfig, + TConfigInput = TConfig, + UOutput extends AnyExtensionDataRef = AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + } = {}, + TKind extends string | undefined = string | undefined, + TNamespace extends string | undefined = string | undefined, + TName extends string | undefined = string | undefined, +> = ExtensionDefinition< + TConfig, + TConfigInput, + UOutput, + TInputs, + TKind, + TNamespace, + TName +> & + ( + | { + readonly version: 'v1'; + readonly inputs: AnyExtensionInputMap; + readonly output: AnyExtensionDataMap; + factory(context: { + node: AppNode; + config: TConfig; + inputs: ResolvedExtensionInputs; + }): ExtensionDataValues; + } + | { + readonly version: 'v2'; + readonly inputs: { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }; + readonly output: Array; + factory(context: { + node: AppNode; + config: TConfig; + inputs: ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput< AnyExtensionDataRef, { optional: boolean; singleton: boolean } >; - }; - readonly output: Array; - factory(context: { - node: AppNode; - config: TConfig; - inputs: ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }>; - }): Iterable>; - } - ); + }>; + }): Iterable>; + } + ); /** @internal */ export function toInternalExtensionDefinition( @@ -357,8 +386,14 @@ export function createExtension< }, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, >( options: CreateExtensionOptions< + TKind, + TNamespace, + TName, UOutput, TInputs, TConfigSchema, @@ -374,7 +409,10 @@ export function createExtension< }> >, UOutput, - TInputs + TInputs, + string | undefined extends TKind ? undefined : TKind, + string | undefined extends TNamespace ? undefined : TNamespace, + string | undefined extends TName ? undefined : TName >; /** * @public @@ -394,6 +432,9 @@ export function createExtension< >, ): ExtensionDefinition; export function createExtension< + const TKind extends string | undefined, + const TNamespace extends string | undefined, + const TName extends string | undefined, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -408,7 +449,15 @@ export function createExtension< UFactoryOutput extends ExtensionDataValue, >( options: - | CreateExtensionOptions + | CreateExtensionOptions< + TKind, + TNamespace, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + > | LegacyCreateExtensionOptions< AnyExtensionDataMap, TLegacyInputs, @@ -431,7 +480,10 @@ export function createExtension< }> >), UOutput, - TInputs + TInputs, + TKind, + TNamespace, + TName > { if ('configSchema' in options && 'config' in options) { throw new Error(`Cannot provide both configSchema and config.schema`); @@ -542,7 +594,10 @@ export function createExtension< > >, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs + TInputs & TExtraInputs, + TKind, + TNamespace, + TName > => { if (!Array.isArray(options.output)) { throw new Error( @@ -550,6 +605,9 @@ export function createExtension< ); } const newOptions = options as CreateExtensionOptions< + TKind, + TNamespace, + TName, UOutput, TInputs, TConfigSchema, @@ -612,7 +670,7 @@ export function createExtension< return deduplicatedResult.values() as Iterable; }, - } as CreateExtensionOptions); + } as CreateExtensionOptions); }, } as InternalExtensionDefinition< TConfig & @@ -630,6 +688,11 @@ export function createExtension< z.ZodObject<{ [key in keyof TConfigSchema]: ReturnType; }> - >) + >), + UOutput, + TInputs, + TKind, + TNamespace, + TName >; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index a8bf42c7f8..81137f47e2 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -36,6 +36,9 @@ import { * @public */ export type CreateExtensionBlueprintOptions< + TKind extends string, + TNamespace extends string | undefined, + TName extends string | undefined, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -48,13 +51,13 @@ export type CreateExtensionBlueprintOptions< UFactoryOutput extends ExtensionDataValue, TDataRefs extends { [name in string]: AnyExtensionDataRef }, > = { - kind: string; - namespace?: string | ((params: TParams) => string); + kind: TKind; + namespace?: TNamespace | ((params: TParams) => TNamespace); attachTo: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; output: Array; - name?: string | ((params: TParams) => string); + name?: TName | ((params: TParams) => TName); config?: { schema: TConfigSchema | ((params: TParams) => TConfigSchema); }; @@ -76,6 +79,9 @@ export type CreateExtensionBlueprintOptions< * @public */ export interface ExtensionBlueprint< + TKind extends string, + TNamespace extends string | undefined, + TName extends string | undefined, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -97,6 +103,8 @@ export interface ExtensionBlueprint< * optionally call the original factory with the same params. */ make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, @@ -110,8 +118,8 @@ export interface ExtensionBlueprint< }, >( args: { - namespace?: string; - name?: string; + namespace?: TNewNamespace; + name?: TNewName; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TExtraInputs & { @@ -166,7 +174,12 @@ export interface ExtensionBlueprint< >; }> > & - TConfigInput + TConfigInput, + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + TInputs & TExtraInputs, + TKind, + string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, + string | undefined extends TNewName ? TName : TNewName >; } @@ -198,6 +211,9 @@ export function createDataContainer( * @internal */ class ExtensionBlueprintImpl< + TKind extends string, + TNamespace extends string | undefined, + TName extends string | undefined, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -211,6 +227,9 @@ class ExtensionBlueprintImpl< > { constructor( private readonly options: CreateExtensionBlueprintOptions< + TKind, + TNamespace, + TName, TParams, UOutput, TInputs, @@ -236,9 +255,11 @@ class ExtensionBlueprintImpl< { optional: boolean; singleton: boolean } >; }, + TNewNamespace extends string | undefined = undefined, + TNewName extends string | undefined = undefined, >(args: { - namespace?: string; - name?: string; + namespace?: TNewNamespace; + name?: TNewName; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TExtraInputs; @@ -360,26 +381,7 @@ class ExtensionBlueprintImpl< } throw new Error('Either params or factory must be provided'); }, - } as CreateExtensionOptions) as ExtensionDefinition< - { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - } & { - [key in keyof TConfigSchema]: z.infer>; - }, - z.input< - z.ZodObject< - { - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - } & { - [key in keyof TConfigSchema]: ReturnType; - } - > - > - >; + } as CreateExtensionOptions); } } @@ -400,9 +402,15 @@ export function createExtensionBlueprint< }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends string | undefined = undefined, + TName extends string | undefined = undefined, TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, >( options: CreateExtensionBlueprintOptions< + TKind, + TNamespace, + TName, TParams, UOutput, TInputs, @@ -411,6 +419,9 @@ export function createExtensionBlueprint< TDataRefs >, ): ExtensionBlueprint< + TKind, + TNamespace, + TName, TParams, UOutput, string extends keyof TInputs ? {} : TInputs, @@ -427,6 +438,9 @@ export function createExtensionBlueprint< TDataRefs > { return new ExtensionBlueprintImpl(options) as ExtensionBlueprint< + TKind, + TNamespace, + TName, TParams, UOutput, string extends keyof TInputs ? {} : TInputs, diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index bb9bdc2107..0242e2961a 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -34,11 +34,9 @@ const nameExtensionDataRef = createExtensionDataRef().with({ const Extension1 = createExtension({ name: '1', attachTo: { id: 'test/output', input: 'names' }, - output: { - name: nameExtensionDataRef, - }, + output: [nameExtensionDataRef], factory() { - return { name: 'extension-1' }; + return [nameExtensionDataRef('extension-1')]; }, }); @@ -150,6 +148,8 @@ describe('createPlugin', () => { }); expect(plugin).toBeDefined(); + expect(plugin.getExtension('test/1')).toBe(Extension1); + await renderWithEffects( createTestAppRoot({ features: [plugin], diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts index b671d95d2f..1d2a12ffe2 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.ts @@ -17,6 +17,7 @@ import { ExtensionDefinition } from './createExtension'; import { Extension, + ResolveExtensionId, resolveExtensionDefinition, } from './resolveExtensionDefinition'; import { @@ -28,21 +29,23 @@ import { /** @public */ export interface PluginOptions< - Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes, + TId extends string, + TRoutes extends AnyRoutes, + TExternalRoutes extends AnyExternalRoutes, + TExtensions extends readonly ExtensionDefinition[], > { - id: string; - routes?: Routes; - externalRoutes?: ExternalRoutes; - extensions?: ExtensionDefinition[]; + id: TId; + routes?: TRoutes; + externalRoutes?: TExternalRoutes; + extensions?: TExtensions; featureFlags?: FeatureFlagConfig[]; } /** @public */ export interface InternalBackstagePlugin< - Routes extends AnyRoutes = AnyRoutes, - ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, -> extends BackstagePlugin { + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, +> extends BackstagePlugin { readonly version: 'v1'; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; @@ -50,17 +53,36 @@ export interface InternalBackstagePlugin< /** @public */ export function createPlugin< - Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {}, + TId extends string, + TRoutes extends AnyRoutes = {}, + TExternalRoutes extends AnyExternalRoutes = {}, + TExtensions extends readonly ExtensionDefinition[] = [], >( - options: PluginOptions, -): BackstagePlugin { - const extensions = (options.extensions ?? []).map(def => - resolveExtensionDefinition(def, { namespace: options.id }), - ); + options: PluginOptions, +): BackstagePlugin< + TRoutes, + TExternalRoutes, + { + [KExtension in TExtensions[number] as ResolveExtensionId< + KExtension, + TId + >]: KExtension; + } +> { + const extensions = new Array>(); + const extensionDefinitionsById = new Map< + string, + ExtensionDefinition + >(); - const extensionIds = extensions.map(e => e.id); - if (extensionIds.length !== new Set(extensionIds).size) { + for (const def of options.extensions ?? []) { + const ext = resolveExtensionDefinition(def, { namespace: options.id }); + extensions.push(ext); + extensionDefinitionsById.set(ext.id, def); + } + + if (extensions.length !== extensionDefinitionsById.size) { + const extensionIds = extensions.map(e => e.id); const duplicates = Array.from( new Set( extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), @@ -78,14 +100,17 @@ export function createPlugin< $$type: '@backstage/BackstagePlugin', version: 'v1', id: options.id, - routes: options.routes ?? ({} as Routes), - externalRoutes: options.externalRoutes ?? ({} as ExternalRoutes), + routes: options.routes ?? ({} as TRoutes), + externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], extensions, + getExtension(id) { + return extensionDefinitionsById.get(id); + }, toString() { return `Plugin{id=${options.id}}`; }, - } as InternalBackstagePlugin; + } as InternalBackstagePlugin; } /** @internal */ diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index 74d081e95d..5a42e9100e 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -15,7 +15,10 @@ */ import { ExtensionDefinition } from './createExtension'; -import { resolveExtensionDefinition } from './resolveExtensionDefinition'; +import { + ResolveExtensionId, + resolveExtensionDefinition, +} from './resolveExtensionDefinition'; describe('resolveExtensionDefinition', () => { const baseDef = { @@ -98,3 +101,103 @@ describe('old resolveExtensionDefinition', () => { ); }); }); + +describe('ResolveExtensionId', () => { + it('should resolve extension IDs correctly', () => { + type NamedExtension< + TKind extends string | undefined, + TNamespace extends string | undefined, + TName extends string | undefined, + > = ExtensionDefinition; + + const id1: 'k:ns' = {} as ResolveExtensionId< + NamedExtension<'k', 'ns', undefined>, + undefined + >; + const id2: 'k:n' = {} as ResolveExtensionId< + NamedExtension<'k', undefined, 'n'>, + undefined + >; + const id3: 'ns/n' = {} as ResolveExtensionId< + NamedExtension, + undefined + >; + const id4: never = {} as ResolveExtensionId< + NamedExtension<'k', undefined, undefined>, + undefined + >; + const id5: 'ns' = {} as ResolveExtensionId< + NamedExtension, + undefined + >; + const id6: 'n' = {} as ResolveExtensionId< + NamedExtension, + undefined + >; + const id7: 'k:ns/n' = {} as ResolveExtensionId< + NamedExtension<'k', 'ns', 'n'>, + undefined + >; + const id8: 'k:ns2' = {} as ResolveExtensionId< + NamedExtension<'k', 'ns', undefined>, + 'ns2' + >; + const id9: 'k:ns2/n' = {} as ResolveExtensionId< + NamedExtension<'k', undefined, 'n'>, + 'ns2' + >; + const ida: 'ns2/n' = {} as ResolveExtensionId< + NamedExtension, + 'ns2' + >; + const idb: 'k:ns2' = {} as ResolveExtensionId< + NamedExtension<'k', undefined, undefined>, + 'ns2' + >; + const idc: 'ns2' = {} as ResolveExtensionId< + NamedExtension, + 'ns2' + >; + const idd: 'ns2/n' = {} as ResolveExtensionId< + NamedExtension, + 'ns2' + >; + const ide: 'k:ns2/n' = {} as ResolveExtensionId< + NamedExtension<'k', 'ns', 'n'>, + 'ns2' + >; + + const invalid1: never = {} as ResolveExtensionId< + NamedExtension, + undefined + >; + const invalid2: never = {} as ResolveExtensionId< + NamedExtension<'k', string | undefined, 'n'>, + undefined + >; + const invalid3: never = {} as ResolveExtensionId< + NamedExtension<'k', 'ns', string | undefined>, + undefined + >; + + expect([ + id1, + id2, + id3, + id4, + id5, + id6, + id7, + id8, + id9, + ida, + idb, + idc, + idd, + ide, + invalid1, + invalid2, + invalid3, + ]).toBeDefined(); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 0c8a7959f7..35bf9c6b05 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -94,6 +94,36 @@ export function toInternalExtension( return internal; } +/** @ignore */ +export type ResolveExtensionId< + TExtension extends ExtensionDefinition, + TDefaultNamespace extends string | undefined, +> = TExtension extends ExtensionDefinition< + any, + any, + any, + any, + infer IKind, + infer INamespace, + infer IName +> + ? [string | undefined] extends [IKind | INamespace | IName] + ? never + : ( + ( + undefined extends TDefaultNamespace ? INamespace : TDefaultNamespace + ) extends infer ISelectedNamespace extends string + ? undefined extends IName + ? ISelectedNamespace + : `${ISelectedNamespace}/${IName}` + : IName + ) extends infer INamePart extends string + ? IKind extends string + ? `${IKind}:${INamePart}` + : INamePart + : never + : never; + /** @internal */ export function resolveExtensionDefinition( definition: ExtensionDefinition, diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index 7e0650e06c..b1afd4e7ea 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -15,6 +15,7 @@ */ import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing'; +import { ExtensionDefinition } from './createExtension'; /** * Feature flag configuration. @@ -32,15 +33,24 @@ export type AnyRoutes = { [name in string]: RouteRef | SubRouteRef }; /** @public */ export type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; +/** @public */ +export type ExtensionMap< + TExtensionMap extends { [id in string]: ExtensionDefinition }, +> = { + get(id: TId): TExtensionMap[TId]; +}; + /** @public */ export interface BackstagePlugin< - Routes extends AnyRoutes = AnyRoutes, - ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, > { readonly $$type: '@backstage/BackstagePlugin'; readonly id: string; - readonly routes: Routes; - readonly externalRoutes: ExternalRoutes; + readonly routes: TRoutes; + readonly externalRoutes: TExternalRoutes; + getExtension(id: TId): TExtensionMap[TId]; } /** @public */ diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md index f17ba0fa3c..6003056cb3 100644 --- a/plugins/api-docs/api-report-alpha.md +++ b/plugins/api-docs/api-report-alpha.md @@ -14,7 +14,8 @@ const _default: BackstagePlugin< }, { registerApi: ExternalRouteRef; - } + }, + {} >; export default _default; diff --git a/plugins/app-visualizer/api-report.md b/plugins/app-visualizer/api-report.md index 764caa7190..fd3749b159 100644 --- a/plugins/app-visualizer/api-report.md +++ b/plugins/app-visualizer/api-report.md @@ -6,7 +6,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const visualizerPlugin: BackstagePlugin<{}, {}>; +const visualizerPlugin: BackstagePlugin<{}, {}, {}>; export default visualizerPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index 88b3a38b76..e651946463 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -18,7 +18,8 @@ const _default: BackstagePlugin< kind: string; namespace: string; }>; - } + }, + {} >; export default _default; diff --git a/plugins/catalog-import/api-report-alpha.md b/plugins/catalog-import/api-report-alpha.md index 63e2cda339..51e3d99fd7 100644 --- a/plugins/catalog-import/api-report-alpha.md +++ b/plugins/catalog-import/api-report-alpha.md @@ -11,6 +11,7 @@ const _default: BackstagePlugin< { importPage: RouteRef; }, + {}, {} >; export default _default; diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index 3ff0788b04..edfa183903 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -115,7 +115,15 @@ export function createEntityCardExtension< config: TConfig; inputs: Expand>; }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition< + TConfig, + TConfig, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @alpha (undocumented) export function createEntityContentExtension< @@ -150,7 +158,10 @@ export function createEntityContentExtension< path?: string | undefined; }, never, - never + never, + string | undefined, + string | undefined, + string | undefined >; // @alpha diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 726ee80573..5019fc25ea 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -108,7 +108,15 @@ export function createCatalogFilterExtension< inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition< + TConfig, + TConfig, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @alpha (undocumented) const _default: BackstagePlugin< @@ -132,7 +140,8 @@ const _default: BackstagePlugin< templateName: string; }>; unregisterRedirect: ExternalRouteRef; - } + }, + {} >; export default _default; diff --git a/plugins/devtools/api-report-alpha.md b/plugins/devtools/api-report-alpha.md index 59aefd3698..23ede8144c 100644 --- a/plugins/devtools/api-report-alpha.md +++ b/plugins/devtools/api-report-alpha.md @@ -11,6 +11,7 @@ const _default: BackstagePlugin< { root: RouteRef; }, + {}, {} >; export default _default; diff --git a/plugins/home/api-report-alpha.md b/plugins/home/api-report-alpha.md index eb693a8e6f..0f9b980a78 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -7,7 +7,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -const _default: BackstagePlugin<{}, {}>; +const _default: BackstagePlugin<{}, {}, {}>; export default _default; // @alpha (undocumented) diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md index 8bd6e98baa..befbcf379a 100644 --- a/plugins/kubernetes/api-report-alpha.md +++ b/plugins/kubernetes/api-report-alpha.md @@ -11,6 +11,7 @@ const _default: BackstagePlugin< { kubernetes: RouteRef; }, + {}, {} >; export default _default; diff --git a/plugins/org/api-report-alpha.md b/plugins/org/api-report-alpha.md index e7c4db2c6d..be74cde141 100644 --- a/plugins/org/api-report-alpha.md +++ b/plugins/org/api-report-alpha.md @@ -11,7 +11,8 @@ const _default: BackstagePlugin< {}, { catalogIndex: ExternalRouteRef; - } + }, + {} >; export default _default; diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index 4628563be2..de0fc463a9 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -36,7 +36,8 @@ const _default: BackstagePlugin< kind: string; namespace: string; }>; - } + }, + {} >; export default _default; diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index 56c0b6eab9..4bcdbb3f78 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -25,7 +25,15 @@ export function createSearchResultListItemExtension< }, >( options: SearchResultItemExtensionOptions, -): ExtensionDefinition; +): ExtensionDefinition< + TConfig, + TConfig, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @alpha (undocumented) export namespace createSearchResultListItemExtension { diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index bb670522e4..e6f91d6d19 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -13,12 +13,21 @@ const _default: BackstagePlugin< { root: RouteRef; }, + {}, {} >; export default _default; // @alpha (undocumented) -export const searchApi: ExtensionDefinition<{}, {}, never, never>; +export const searchApi: ExtensionDefinition< + {}, + {}, + never, + never, + string | undefined, + string | undefined, + string | undefined +>; // @alpha (undocumented) export const searchNavItem: ExtensionDefinition< @@ -29,7 +38,10 @@ export const searchNavItem: ExtensionDefinition< title?: string | undefined; }, never, - never + never, + string | undefined, + string | undefined, + string | undefined >; // @alpha (undocumented) @@ -43,7 +55,10 @@ export const searchPage: ExtensionDefinition< noTrack: boolean; }, AnyExtensionDataRef, - {} + {}, + string | undefined, + string | undefined, + string | undefined >; // (No @packageDocumentation comment for this package) diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index e3c48ba214..f7c6679139 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -18,6 +18,7 @@ const _default: BackstagePlugin< }>; entityContent: RouteRef; }, + {}, {} >; export default _default; @@ -39,7 +40,10 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition< title?: string | undefined; }, never, - never + never, + string | undefined, + string | undefined, + string | undefined >; // (No @packageDocumentation comment for this package) diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index d0fad90e08..700e06a7ad 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -13,6 +13,7 @@ const _default: BackstagePlugin< { root: RouteRef; }, + {}, {} >; export default _default; @@ -26,7 +27,10 @@ export const settingsNavItem: ExtensionDefinition< title?: string | undefined; }, never, - never + never, + string | undefined, + string | undefined, + string | undefined >; // @alpha (undocumented) From 7e8463743aa6853781733bcc01f528b9b31c9d9b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 9 Aug 2024 10:26:24 +0200 Subject: [PATCH 103/393] frontend-plugin-api: add test to make sure getExtension complains if ID is unknown Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/src/wiring/createPlugin.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index 0242e2961a..76cc030be3 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -149,6 +149,8 @@ describe('createPlugin', () => { expect(plugin).toBeDefined(); expect(plugin.getExtension('test/1')).toBe(Extension1); + // @ts-expect-error + expect(plugin.getExtension('nonexistent')).toBeUndefined(); await renderWithEffects( createTestAppRoot({ From d1191ee2d76a95a25bebe3860ca564032a6b3378 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 14:52:04 +0200 Subject: [PATCH 104/393] feat: added a new PageBlueprint Signed-off-by: blam Signed-off-by: blam --- .../src/extensions/PageBlueprint.test.tsx | 154 ++++++++++++++++++ .../src/extensions/PageBlueprint.tsx | 71 ++++++++ 2 files changed, 225 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx new file mode 100644 index 0000000000..abe2d96a85 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx @@ -0,0 +1,154 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { createRouteRef } from '../routing'; +import { PageBlueprint } from './PageBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + coreExtensionData, + createExtensionBlueprint, + createExtensionInput, +} from '../wiring'; +import { waitFor } from '@testing-library/react'; + +describe('PageBlueprint', () => { + const mockRouteRef = createRouteRef(); + + it('should return an extension when calling make with sensible defaults', () => { + const myPage = PageBlueprint.make({ + name: 'test-page', + params: { + loader: () => Promise.resolve(
Test
), + defaultPath: '/test', + routeRef: mockRouteRef, + }, + }); + + expect(myPage).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/routes", + "input": "routes", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "page", + "name": "test-page", + "namespace": undefined, + "output": [ + [Function], + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.routing.ref", + "optional": [Function], + "toString": [Function], + }, + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should output a routeRef with the input routeRef', async () => { + const myPage = PageBlueprint.make({ + name: 'test-page', + params: { + loader: () => Promise.resolve(
Test
), + defaultPath: '/test', + routeRef: mockRouteRef, + }, + }); + + const tester = createExtensionTester(myPage); + + // TODO(blam): test for the routePath output doesn't work. + // expect(tester.data(coreExtensionData.routePath)).toBe('/test'); + + expect(tester.data(coreExtensionData.routeRef)).toBe(mockRouteRef); + + const { getByTestId } = tester.render(); + + await waitFor(() => expect(getByTestId('test')).toBeInTheDocument()); + }); + + it('should allow defining additional inputs to the extension', async () => { + const myPage = PageBlueprint.make({ + name: 'test-page', + params: { + loader: async ({ inputs }) => { + return ( +
+ {/* todo(blam): need to fix the typescript here, as inputs is not the right type */} + {inputs.cards.map(c => c.get(coreExtensionData.reactElement))} +
+ ); + }, + defaultPath: '/test', + routeRef: mockRouteRef, + }, + inputs: { + cards: createExtensionInput([coreExtensionData.reactElement], { + optional: false, + singleton: false, + }), + }, + }); + + const CardBlueprint = createExtensionBlueprint({ + kind: 'card', + attachTo: { id: 'page:test-page', input: 'cards' }, + output: [coreExtensionData.reactElement], + factory() { + return [ + coreExtensionData.reactElement( +
I'm a lovely card
, + ), + ]; + }, + }); + + const tester = createExtensionTester(myPage).add( + CardBlueprint.make({ name: 'card', params: {} }), + ); + + const { getByTestId, getByText } = tester.render(); + + await waitFor(() => expect(getByTestId('card')).toBeInTheDocument()); + await waitFor(() => + expect(getByText("I'm a lovely card")).toBeInTheDocument(), + ); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx new file mode 100644 index 0000000000..7ae26cf387 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { lazy } from 'react'; +import { RouteRef } from '../routing'; +import { coreExtensionData, createExtensionBlueprint } from '../wiring'; +import { ExtensionBoundary } from '../components'; + +export const PageBlueprint = createExtensionBlueprint({ + kind: 'page', + attachTo: { id: 'app/routes', input: 'routes' }, + output: [ + coreExtensionData.routePath, + coreExtensionData.reactElement, + coreExtensionData.routeRef.optional(), + ], + config: { + schema: { + path: z => z.string().optional(), + }, + }, + factory( + { + defaultPath, + loader, + routeRef, + }: { + defaultPath?: string; + loader: (opts: { + config: typeof config; + inputs: typeof inputs; + }) => Promise; + routeRef?: RouteRef; + }, + { config, inputs, node }, + ) { + const ExtensionComponent = lazy(() => + loader({ config, inputs }).then(element => ({ default: () => element })), + ); + + // TODO(blam): this is a little awkward for optional returns. + // I wonder if we should be using generators or yield instead + // for a better API here. + const outputs = [ + coreExtensionData.routePath(config.path ?? defaultPath!), + coreExtensionData.reactElement( + + + , + ), + ]; + + if (routeRef) { + return [...outputs, coreExtensionData.routeRef(routeRef)]; + } + + return outputs; + }, +}); From 8897c29ecc13ec2aa70dec12baeb6b944f33aad8 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 15:46:39 +0200 Subject: [PATCH 105/393] chore: migrate ThemeCreator Signed-off-by: blam --- .../src/extensions/ThemeBlueprint.test.ts | 64 +++++++++++++++++++ .../src/extensions/ThemeBlueprint.ts | 32 ++++++++++ 2 files changed, 96 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts create mode 100644 packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts new file mode 100644 index 0000000000..d3d05a5e68 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { AppTheme } from '@backstage/core-plugin-api'; +import { ThemeBlueprint } from './ThemeBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; + +describe('ThemeBlueprint', () => { + const theme = { + id: 'light', + colors: { primary: 'blue' }, + variant: 'dark', + title: 'lols', + Provider: (_: { children: React.ReactNode }) => null, + } as AppTheme; + + it('should create an extension with sensible defaults', () => { + expect( + // todo(blam): we can't inject theme.id as the name here like the old extension creator. + // Wonder if theres a better solution. + ThemeBlueprint.make({ name: 'blob', params: { theme } }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app", + "input": "themes", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "theme", + "name": "blob", + "namespace": "app", + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return the theme as an themeDataRef', async () => { + const extension = ThemeBlueprint.make({ name: 'blob', params: { theme } }); + + expect( + createExtensionTester(extension).data(ThemeBlueprint.dataRefs.theme), + ).toEqual(theme); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts new file mode 100644 index 0000000000..4ddb5a298a --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppTheme } from '@backstage/core-plugin-api'; +import { createExtensionBlueprint } from '../wiring'; +import { createThemeExtension } from './createThemeExtension'; + +export const ThemeBlueprint = createExtensionBlueprint({ + kind: 'theme', + namespace: 'app', + attachTo: { id: 'app', input: 'themes' }, + output: [createThemeExtension.themeDataRef], + dataRefs: { + theme: createThemeExtension.themeDataRef, + }, + factory: ({ theme }: { theme: AppTheme }) => [ + createThemeExtension.themeDataRef(theme), + ], +}); From da75ca4e4702b057b8c3bd23bd6e6e514b8a8e77 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 16:21:57 +0200 Subject: [PATCH 106/393] chore: migration translation extension creator Signed-off-by: blam --- .../extensions/TranslationBlueprint.test.ts | 81 +++++++++++++++++++ .../src/extensions/TranslationBlueprint.ts | 33 ++++++++ 2 files changed, 114 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts create mode 100644 packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts new file mode 100644 index 0000000000..fe7aeba59d --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + createTranslationMessages, + createTranslationRef, +} from '../translation'; +import { TranslationBlueprint } from './TranslationBlueprint'; + +describe('TranslationBlueprint', () => { + const translationRef = createTranslationRef({ + id: 'test', + messages: { + test: 'test', + }, + }); + + const messages = createTranslationMessages({ + ref: translationRef, + messages: { + test: 'test2', + }, + }); + + it('should return an extension instance with sane defaults', () => { + expect( + TranslationBlueprint.make({ + params: { + resource: messages, + }, + }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app", + "input": "translations", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "translation", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should output a translation data ref', () => { + const extension = TranslationBlueprint.make({ + params: { + resource: messages, + }, + }); + + expect( + createExtensionTester(extension).data( + TranslationBlueprint.dataRefs.translation, + ), + ).toBe(messages); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts new file mode 100644 index 0000000000..23cb5da6a6 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtensionBlueprint } from '../wiring'; +import { createTranslationExtension } from './createTranslationExtension'; +import { TranslationMessages, TranslationResource } from '../translation'; + +export const TranslationBlueprint = createExtensionBlueprint({ + kind: 'translation', + attachTo: { id: 'app', input: 'translations' }, + output: [createTranslationExtension.translationDataRef], + dataRefs: { + translation: createTranslationExtension.translationDataRef, + }, + factory: ({ + resource, + }: { + resource: TranslationResource | TranslationMessages; + }) => [createTranslationExtension.translationDataRef(resource)], +}); From 48b4304c46a62052ad2245b52def395ca0256aee Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 1 Aug 2024 16:28:33 +0200 Subject: [PATCH 107/393] chore: fix Signed-off-by: blam Signed-off-by: blam --- .../src/extensions/TranslationBlueprint.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts index fe7aeba59d..658c0c9709 100644 --- a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts @@ -38,6 +38,10 @@ describe('TranslationBlueprint', () => { it('should return an extension instance with sane defaults', () => { expect( TranslationBlueprint.make({ + // todo(blam): we can't set the namespace dynamically based of the ResourceType. + // work out if we should wrap this up or another solution. + namespace: messages.id, + name: 'test', params: { resource: messages, }, @@ -54,8 +58,8 @@ describe('TranslationBlueprint', () => { "factory": [Function], "inputs": {}, "kind": "translation", - "name": undefined, - "namespace": undefined, + "name": "test", + "namespace": "test", "output": [ [Function], ], From 1f4029b9d877885db10ba63a57afee368bb906a5 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 08:50:47 +0200 Subject: [PATCH 108/393] chore: refactor a little bit and use some new features to remove todos Signed-off-by: blam --- .../src/extensions/PageBlueprint.test.tsx | 4 ++-- .../src/extensions/PageBlueprint.tsx | 23 +++++++------------ .../src/extensions/ThemeBlueprint.test.ts | 10 +++----- .../src/extensions/ThemeBlueprint.ts | 1 + .../extensions/TranslationBlueprint.test.ts | 12 ++++------ .../src/extensions/TranslationBlueprint.ts | 1 + 6 files changed, 20 insertions(+), 31 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx index abe2d96a85..e020167f43 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx @@ -94,7 +94,7 @@ describe('PageBlueprint', () => { const tester = createExtensionTester(myPage); - // TODO(blam): test for the routePath output doesn't work. + // TODO(blam): test for the routePath output doesn't work, due to the the way the test harness works // expect(tester.data(coreExtensionData.routePath)).toBe('/test'); expect(tester.data(coreExtensionData.routeRef)).toBe(mockRouteRef); @@ -111,7 +111,6 @@ describe('PageBlueprint', () => { loader: async ({ inputs }) => { return (
- {/* todo(blam): need to fix the typescript here, as inputs is not the right type */} {inputs.cards.map(c => c.get(coreExtensionData.reactElement))}
); @@ -119,6 +118,7 @@ describe('PageBlueprint', () => { defaultPath: '/test', routeRef: mockRouteRef, }, + /* todo(blam): need to fix the typescript here, as inputs is not the right type, wont let me merge without specifying parent opts */ inputs: { cards: createExtensionInput([coreExtensionData.reactElement], { optional: false, diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx index 7ae26cf387..4ee26321b7 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx @@ -31,7 +31,7 @@ export const PageBlueprint = createExtensionBlueprint({ path: z => z.string().optional(), }, }, - factory( + *factory( { defaultPath, loader, @@ -50,22 +50,15 @@ export const PageBlueprint = createExtensionBlueprint({ loader({ config, inputs }).then(element => ({ default: () => element })), ); - // TODO(blam): this is a little awkward for optional returns. - // I wonder if we should be using generators or yield instead - // for a better API here. - const outputs = [ - coreExtensionData.routePath(config.path ?? defaultPath!), - coreExtensionData.reactElement( - - - , - ), - ]; + yield coreExtensionData.routePath(config.path ?? defaultPath!); + yield coreExtensionData.reactElement( + + + , + ); if (routeRef) { - return [...outputs, coreExtensionData.routeRef(routeRef)]; + yield coreExtensionData.routeRef(routeRef); } - - return outputs; }, }); diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts index d3d05a5e68..5bc3a8f7c3 100644 --- a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts @@ -27,11 +27,7 @@ describe('ThemeBlueprint', () => { } as AppTheme; it('should create an extension with sensible defaults', () => { - expect( - // todo(blam): we can't inject theme.id as the name here like the old extension creator. - // Wonder if theres a better solution. - ThemeBlueprint.make({ name: 'blob', params: { theme } }), - ).toMatchInlineSnapshot(` + expect(ThemeBlueprint.make({ params: { theme } })).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", "attachTo": { @@ -43,7 +39,7 @@ describe('ThemeBlueprint', () => { "factory": [Function], "inputs": {}, "kind": "theme", - "name": "blob", + "name": "light", "namespace": "app", "output": [ [Function], @@ -55,7 +51,7 @@ describe('ThemeBlueprint', () => { }); it('should return the theme as an themeDataRef', async () => { - const extension = ThemeBlueprint.make({ name: 'blob', params: { theme } }); + const extension = ThemeBlueprint.make({ params: { theme } }); expect( createExtensionTester(extension).data(ThemeBlueprint.dataRefs.theme), diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts index 4ddb5a298a..500946713b 100644 --- a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts @@ -21,6 +21,7 @@ import { createThemeExtension } from './createThemeExtension'; export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', namespace: 'app', + name: ({ theme }) => theme.id, attachTo: { id: 'app', input: 'themes' }, output: [createThemeExtension.themeDataRef], dataRefs: { diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts index 658c0c9709..a059189044 100644 --- a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts @@ -22,7 +22,7 @@ import { TranslationBlueprint } from './TranslationBlueprint'; describe('TranslationBlueprint', () => { const translationRef = createTranslationRef({ - id: 'test', + id: 'translationRefId', messages: { test: 'test', }, @@ -38,10 +38,7 @@ describe('TranslationBlueprint', () => { it('should return an extension instance with sane defaults', () => { expect( TranslationBlueprint.make({ - // todo(blam): we can't set the namespace dynamically based of the ResourceType. - // work out if we should wrap this up or another solution. - namespace: messages.id, - name: 'test', + name: 'blob', params: { resource: messages, }, @@ -58,8 +55,8 @@ describe('TranslationBlueprint', () => { "factory": [Function], "inputs": {}, "kind": "translation", - "name": "test", - "namespace": "test", + "name": "blob", + "namespace": "translationRefId", "output": [ [Function], ], @@ -71,6 +68,7 @@ describe('TranslationBlueprint', () => { it('should output a translation data ref', () => { const extension = TranslationBlueprint.make({ + name: 'blob', params: { resource: messages, }, diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts index 23cb5da6a6..581f4d176a 100644 --- a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts +++ b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts @@ -20,6 +20,7 @@ import { TranslationMessages, TranslationResource } from '../translation'; export const TranslationBlueprint = createExtensionBlueprint({ kind: 'translation', + namespace: ({ resource }) => resource.id, attachTo: { id: 'app', input: 'translations' }, output: [createTranslationExtension.translationDataRef], dataRefs: { From 593fdd4e007c072c37a7d3ba94734dad0ad115b0 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 09:14:05 +0200 Subject: [PATCH 109/393] chore: added SignInPageBlueprint Signed-off-by: blam --- .../extensions/SignInPageBlueprint.test.tsx | 67 +++++++++++++++++++ .../src/extensions/SignInPageBlueprint.tsx | 50 ++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx new file mode 100644 index 0000000000..7bbeb4e5d7 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { SignInPageBlueprint } from './SignInPageBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { waitFor } from '@testing-library/react'; + +describe('SignInPageBlueprint', () => { + it('should create an extension with sensible defaults', () => { + expect( + SignInPageBlueprint.make({ + params: { loader: async () => () =>
}, + }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/root", + "input": "signInPage", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "sign-in-page", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return the component as the componentRef', async () => { + const MockSignInPage = () =>
MockSignInPage
; + + const extension = SignInPageBlueprint.make({ + params: { loader: async () => () => }, + }); + + const tester = createExtensionTester(extension); + expect(tester.data(SignInPageBlueprint.dataRefs.component)).toBeDefined(); + + const { getByText } = tester.render(); + + // todo(blam): need a better way to test this, currently fails. + await waitFor(() => { + expect(getByText('MockSignInPage')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx new file mode 100644 index 0000000000..01ba174efe --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { ComponentType, lazy } from 'react'; +import { createExtensionBlueprint } from '../wiring'; +import { createSignInPageExtension } from './createSignInPageExtension'; +import { SignInPageProps } from '@backstage/core-plugin-api'; +import { ExtensionBoundary } from '../components'; + +export const SignInPageBlueprint = createExtensionBlueprint({ + kind: 'sign-in-page', + attachTo: { id: 'app/root', input: 'signInPage' }, + output: [createSignInPageExtension.componentDataRef], + dataRefs: { + component: createSignInPageExtension.componentDataRef, + }, + *factory( + { + loader, + }: { + loader: (opts: { + config: typeof config; + inputs: typeof inputs; + }) => Promise>; + }, + { config, inputs, node }, + ) { + const ExtensionComponent = lazy(() => + loader({ config, inputs }).then(component => ({ default: component })), + ); + + yield createSignInPageExtension.componentDataRef(props => ( + + + + )); + }, +}); From ac136771b2ca4331b6f54f32cea3657d9b52f827 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 09:34:12 +0200 Subject: [PATCH 110/393] feat: added RouterBlueprint Signed-off-by: blam --- .../src/extensions/RouterBlueprint.test.tsx | 167 ++++++++++++++++++ .../src/extensions/RouterBlueprint.tsx | 45 +++++ 2 files changed, 212 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx new file mode 100644 index 0000000000..3abc967fd8 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx @@ -0,0 +1,167 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { RouterBlueprint } from './RouterBlueprint'; +import { MemoryRouter } from 'react-router-dom'; +import { render, waitFor } from '@testing-library/react'; +import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { + coreExtensionData, + createExtension, + createExtensionInput, + createExtensionOverrides, +} from '../wiring'; +import { MockConfigApi } from '@backstage/test-utils'; +import { PageBlueprint } from './PageBlueprint'; + +describe('RouterBlueprint', () => { + it('should return an extension when calling make with sensible defaults', () => { + const extension = RouterBlueprint.make({ + params: { + Component: props =>
{props.children}
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/root", + "input": "router", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "app-router-component", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should work with simple options', async () => { + const extension = RouterBlueprint.make({ + namespace: 'test', + params: { + Component: ({ children }) => ( + +
{children}
+
+ ), + }, + }); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + extension, + PageBlueprint.make({ + namespace: 'test', + params: { + defaultPath: '/', + loader: async () =>
, + }, + }), + ], + }), + ], + }); + + const { getByTestId } = render(app.createRoot()); + + await waitFor(() => { + expect(getByTestId('test-contents')).toBeInTheDocument(); + expect(getByTestId('test-router')).toBeInTheDocument(); + }); + }); + + it('should work with complex options and props', async () => { + const extension = RouterBlueprint.make({ + namespace: 'test', + name: 'test', + config: { + schema: { + name: z => z.string(), + }, + }, + inputs: { + children: createExtensionInput([coreExtensionData.reactElement]), + }, + params: { + Component: ({ inputs, children, config }) => ( + +
+ {children} +
+
+ ), + }, + }); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + extension, + createExtension({ + namespace: 'test', + attachTo: { + id: 'app-router-component:test/test', + input: 'children', + }, + output: [coreExtensionData.reactElement], + *factory() { + yield coreExtensionData.reactElement(
); + }, + }), + PageBlueprint.make({ + namespace: 'test', + params: { + defaultPath: '/', + loader: async () =>
, + }, + }), + ], + }), + ], + config: new MockConfigApi({ + app: { + extensions: [ + { + 'app-router-component:test/test': { config: { name: 'Robin' } }, + }, + ], + }, + }), + }); + + const { getByTestId } = render(app.createRoot()); + + await waitFor(() => { + expect(getByTestId('test-contents')).toBeInTheDocument(); + expect(getByTestId('test-router-Robin-1')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx new file mode 100644 index 0000000000..9e9cddbdd1 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { ComponentType, PropsWithChildren } from 'react'; +import { createExtensionBlueprint } from '../wiring'; +import { createRouterExtension } from './createRouterExtension'; + +export const RouterBlueprint = createExtensionBlueprint({ + kind: 'app-router-component', + attachTo: { id: 'app/root', input: 'router' }, + output: [createRouterExtension.componentDataRef], + *factory( + { + Component, + }: { + Component: ComponentType< + PropsWithChildren<{ + inputs: typeof inputs; + config: typeof config; + }> + >; + }, + { config, inputs }, + ) { + const Wrapper = (props: PropsWithChildren<{}>) => ( + + {props.children} + + ); + + yield createRouterExtension.componentDataRef(Wrapper); + }, +}); From b7506f214ea83a5e833261de20e30ec8e92bbe7b Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 09:53:02 +0200 Subject: [PATCH 111/393] feat: added NavLogoBlueprint Signed-off-by: blam --- .../src/extensions/NavLogoBlueprint.test.tsx | 71 +++++++++++++++++++ .../src/extensions/NavLogoBlueprint.ts | 39 ++++++++++ .../src/extensions/RouterBlueprint.tsx | 3 + .../extensions/SignInPageBlueprint.test.tsx | 6 +- 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts diff --git a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx new file mode 100644 index 0000000000..fa01862785 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { NavLogoBlueprint } from './NavLogoBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; + +describe('NavLogoBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const extension = NavLogoBlueprint.make({ + params: { + logoFull:
Logo Full
, + logoIcon:
Logo Icon
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/nav", + "input": "logos", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "nav-logo", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return a valid component ref', () => { + const logoFull =
Logo Full
; + const logoIcon =
Logo Icon
; + + const extension = NavLogoBlueprint.make({ + name: 'test', + params: { + logoFull, + logoIcon, + }, + }); + + const tester = createExtensionTester(extension); + + expect(tester.data(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ + logoFull, + logoIcon, + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts new file mode 100644 index 0000000000..d119287f29 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtensionBlueprint } from '../wiring'; +import { createNavLogoExtension } from './createNavLogoExtension'; + +export const NavLogoBlueprint = createExtensionBlueprint({ + kind: 'nav-logo', + attachTo: { id: 'app/nav', input: 'logos' }, + output: [createNavLogoExtension.logoElementsDataRef], + dataRefs: { + logoElements: createNavLogoExtension.logoElementsDataRef, + }, + *factory({ + logoIcon, + logoFull, + }: { + logoIcon: JSX.Element; + logoFull: JSX.Element; + }) { + yield createNavLogoExtension.logoElementsDataRef({ + logoIcon, + logoFull, + }); + }, +}); diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx index 9e9cddbdd1..bf910eb944 100644 --- a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx +++ b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx @@ -21,6 +21,9 @@ export const RouterBlueprint = createExtensionBlueprint({ kind: 'app-router-component', attachTo: { id: 'app/root', input: 'router' }, output: [createRouterExtension.componentDataRef], + dataRefs: { + component: createRouterExtension.componentDataRef, + }, *factory( { Component, diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx index 7bbeb4e5d7..11fe1420f1 100644 --- a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx @@ -48,7 +48,7 @@ describe('SignInPageBlueprint', () => { }); it('should return the component as the componentRef', async () => { - const MockSignInPage = () =>
MockSignInPage
; + const MockSignInPage = () =>
; const extension = SignInPageBlueprint.make({ params: { loader: async () => () => }, @@ -57,11 +57,11 @@ describe('SignInPageBlueprint', () => { const tester = createExtensionTester(extension); expect(tester.data(SignInPageBlueprint.dataRefs.component)).toBeDefined(); - const { getByText } = tester.render(); + const { getByTestId } = tester.render(); // todo(blam): need a better way to test this, currently fails. await waitFor(() => { - expect(getByText('MockSignInPage')).toBeInTheDocument(); + expect(getByTestId('mock-sign-in')).toBeInTheDocument(); }); }); }); From b284ecb010272dd725ad043e36cf39e1136e3793 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 11:33:26 +0200 Subject: [PATCH 112/393] chore: reworking how to override the page component Signed-off-by: blam --- .../src/extensions/NavItemBlueprint.ts | 51 +++++++++++++++++++ .../src/extensions/PageBlueprint.test.tsx | 23 ++++----- 2 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts diff --git a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts new file mode 100644 index 0000000000..568431c0f5 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconComponent } from '@backstage/core-plugin-api'; +import { RouteRef } from '../routing'; +import { createExtensionBlueprint } from '../wiring'; +import { createNavItemExtension } from './createNavItemExtension'; + +export const NavItemBlueprint = createExtensionBlueprint({ + kind: 'nav-item', + attachTo: { id: 'app/nav', input: 'items' }, + output: [createNavItemExtension.targetDataRef], + dataRefs: { + target: createNavItemExtension.targetDataRef, + }, + factory: ( + { + icon, + routeRef, + }: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + { config }, + ) => [ + createNavItemExtension.targetDataRef({ + title: config.title, + icon, + routeRef, + }), + ], + config: { + schema: ({ title }) => ({ + title: z => z.string().default(title), + }), + }, +}); diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx index e020167f43..5e1d6b7601 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx @@ -107,24 +107,23 @@ describe('PageBlueprint', () => { it('should allow defining additional inputs to the extension', async () => { const myPage = PageBlueprint.make({ name: 'test-page', - params: { - loader: async ({ inputs }) => { - return ( -
- {inputs.cards.map(c => c.get(coreExtensionData.reactElement))} -
- ); - }, - defaultPath: '/test', - routeRef: mockRouteRef, - }, - /* todo(blam): need to fix the typescript here, as inputs is not the right type, wont let me merge without specifying parent opts */ inputs: { cards: createExtensionInput([coreExtensionData.reactElement], { optional: false, singleton: false, }), }, + factory(originalFactory, { inputs }) { + return originalFactory({ + loader: async () => ( +
+ {inputs.cards.map(c => c.get(coreExtensionData.reactElement))} +
+ ), + defaultPath: '/test', + routeRef: mockRouteRef, + }); + }, }); const CardBlueprint = createExtensionBlueprint({ From 60d1832ce3138cd0e2769f8feaeb33940596ae6d Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 13:23:11 +0200 Subject: [PATCH 113/393] chore: added NavItemBlueprint Signed-off-by: blam --- .../src/extensions/NavItemBlueprint.test.tsx | 106 ++++++++++++++++++ .../extensions/SignInPageBlueprint.test.tsx | 2 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx diff --git a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx new file mode 100644 index 0000000000..ba7643eee7 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx @@ -0,0 +1,106 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { createRouteRef } from '../routing'; +import { NavItemBlueprint } from './NavItemBlueprint'; + +describe('NavItemBlueprint', () => { + const mockRouteRef = createRouteRef(); + const MockIcon = () => null; + + it('should return an extension with sensible defaults', () => { + const extension = NavItemBlueprint.make({ + params: { + icon: MockIcon, + routeRef: mockRouteRef, + title: 'TEST', + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/nav", + "input": "items", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "title": { + "default": "TEST", + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "nav-item", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return the correct extension data', () => { + const extension = NavItemBlueprint.make({ + params: { + icon: MockIcon, + routeRef: mockRouteRef, + title: 'TEST', + }, + }); + + const tester = createExtensionTester(extension); + + expect(tester.data(NavItemBlueprint.dataRefs.target)).toEqual({ + title: 'TEST', + icon: MockIcon, + routeRef: mockRouteRef, + }); + }); + + it('should allow overriding of the title using config', () => { + const extension = NavItemBlueprint.make({ + params: { + icon: MockIcon, + routeRef: mockRouteRef, + title: 'TEST', + }, + }); + + const tester = createExtensionTester(extension, { + config: { title: 'OVERRIDDEN' }, + }); + + expect(tester.data(NavItemBlueprint.dataRefs.target)).toEqual({ + title: 'OVERRIDDEN', + icon: MockIcon, + routeRef: mockRouteRef, + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx index 11fe1420f1..5ffdd3175b 100644 --- a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx @@ -48,7 +48,7 @@ describe('SignInPageBlueprint', () => { }); it('should return the component as the componentRef', async () => { - const MockSignInPage = () =>
; + const MockSignInPage = () =>
; const extension = SignInPageBlueprint.make({ params: { loader: async () => () => }, From 8d5049b5a5515ec4415fac6d7c4847fe6107f1b2 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 7 Aug 2024 13:44:04 +0200 Subject: [PATCH 114/393] chore: wip Signed-off-by: blam --- .../src/extensions/ComponentBlueprint.tsx | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx diff --git a/packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx new file mode 100644 index 0000000000..614196fc09 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentRef } from '../components'; +import { createExtensionBlueprint } from '../wiring'; +import { createComponentExtension } from './createComponentExtension'; +import { lazy, ComponentType } from 'react'; + +// this is hard to do with blueprints... no TProps for the elements +export const ComponentBlueprint = createExtensionBlueprint({ + kind: 'component', + attachTo: { id: 'app', input: 'components' }, + output: [createComponentExtension.componentDataRef], + dataRefs: { + component: createComponentExtension.componentDataRef, + }, + factory( + { + ref, + loader, + }: { + ref: ComponentRef; + loader: + | { + lazy: (values: any) => Promise>; + } + | { + sync: (values: any) => ComponentType; + }; + }, + { config, inputs }, + ) { + if ('sync' in loader) { + return [ + createComponentExtension.componentDataRef({ + ref, + impl: loader.sync({ config, inputs }), + }), + ]; + } + + const lazyLoader = loader.lazy; + const ExtensionComponent = lazy(() => + lazyLoader({ config, inputs }).then(Component => ({ + default: Component, + })), + ); + + return [ + createComponentExtension.componentDataRef({ + ref, + impl: ExtensionComponent, + }), + ]; + }, +}); From 7a4eb9bcb5e213d1b5b018caf751ad14ef025387 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 8 Aug 2024 11:30:24 +0200 Subject: [PATCH 115/393] chore: more work for migrating Signed-off-by: blam --- .../src/extensions/ApiBlueprint.test.ts | 66 +++++++++++++++++++ .../src/extensions/ApiBlueprint.ts | 50 ++++++++++++++ .../src/extensions/PageBlueprint.tsx | 9 +-- 3 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts create mode 100644 packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts new file mode 100644 index 0000000000..99369c6acd --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtensionInput } from '../wiring'; +import { ApiBlueprint } from './ApiBlueprint'; +import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; + +describe('ApiBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const api = createApiRef<{ foo: string }>({ id: 'test' }); + const factory = createApiFactory({ + api, + deps: {}, + factory: () => ({ foo: 'bar' }), + }); + + const extension = ApiBlueprint.make({ + params: { + factory, + }, + }); + + expect(extension).toMatchInlineSnapshot(); + }); + + it('should create an extension with custom factory', () => { + const api = createApiRef<{ foo: string }>({ id: 'test' }); + const factory = jest.fn(() => ({ foo: 'bar' })); + + const extension = ApiBlueprint.make({ + config: { + schema: { + test: z => z.string().default('test'), + }, + }, + inputs: { + test: createExtensionInput([ApiBlueprint.dataRefs.factory]), + }, + factory(originalFactory, { config: _config, inputs: _inputs }) { + return originalFactory({ + api, + factory: () => + createApiFactory({ + api, + deps: {}, + factory, + }), + }); + }, + }); + + expect(extension).toMatchInlineSnapshot(); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts new file mode 100644 index 0000000000..dd2130f2cb --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtensionBlueprint } from '../wiring'; +import { createApiExtension } from './createApiExtension'; +import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; + +export const ApiBlueprint = createExtensionBlueprint({ + kind: 'api', + attachTo: { id: 'app', input: 'apis' }, + output: [createApiExtension.factoryDataRef], + dataRefs: { + factory: createApiExtension.factoryDataRef, + }, + *factory( + params: // remove this form. + | { + api: AnyApiRef; + factory: (params: unknown) => AnyApiFactory; + } + | { + factory: AnyApiFactory; + }, + { config, inputs }, + ) { + yield createApiExtension.factoryDataRef( + typeof params.factory === 'function' + ? params.factory({ config, inputs }) + : params.factory, + ); + }, + namespace: params => { + const apiRef = + 'api' in params ? params.api : (params.factory as { api: AnyApiRef }).api; + + return apiRef.id; + }, +}); diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx index 4ee26321b7..506301ffdf 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx @@ -38,16 +38,13 @@ export const PageBlueprint = createExtensionBlueprint({ routeRef, }: { defaultPath?: string; - loader: (opts: { - config: typeof config; - inputs: typeof inputs; - }) => Promise; + loader: () => Promise; routeRef?: RouteRef; }, - { config, inputs, node }, + { config, node }, ) { const ExtensionComponent = lazy(() => - loader({ config, inputs }).then(element => ({ default: () => element })), + loader().then(element => ({ default: () => element })), ); yield coreExtensionData.routePath(config.path ?? defaultPath!); From d3cfdc6e4dcda5393d61c7db87a388068db50086 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 8 Aug 2024 13:15:56 +0200 Subject: [PATCH 116/393] chore: updating API blueprint Signed-off-by: blam Signed-off-by: blam --- .../src/extensions/ApiBlueprint.test.ts | 81 ++++++++++++++++--- .../src/extensions/ApiBlueprint.ts | 27 +------ .../AppRootElementBlueprint.test.tsx | 48 +++++++++++ .../src/extensions/AppRootElementBlueprint.ts | 27 +++++++ .../src/extensions/ComponentBlueprint.tsx | 69 ---------------- .../src/extensions/RouterBlueprint.test.tsx | 22 ++--- .../src/extensions/RouterBlueprint.tsx | 24 +----- .../src/extensions/SignInPageBlueprint.tsx | 9 +-- .../src/wiring/createExtensionBlueprint.ts | 14 ++-- 9 files changed, 176 insertions(+), 145 deletions(-) create mode 100644 packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts index 99369c6acd..c85c021c08 100644 --- a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts @@ -32,7 +32,27 @@ describe('ApiBlueprint', () => { }, }); - expect(extension).toMatchInlineSnapshot(); + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app", + "input": "apis", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "api", + "name": undefined, + "namespace": "test", + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); }); it('should create an extension with custom factory', () => { @@ -48,19 +68,62 @@ describe('ApiBlueprint', () => { inputs: { test: createExtensionInput([ApiBlueprint.dataRefs.factory]), }, + namespace: api.id, factory(originalFactory, { config: _config, inputs: _inputs }) { return originalFactory({ - api, - factory: () => - createApiFactory({ - api, - deps: {}, - factory, - }), + factory: createApiFactory({ + api, + deps: {}, + factory, + }), }); }, }); - expect(extension).toMatchInlineSnapshot(); + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app", + "input": "apis", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "test": { + "default": "test", + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": { + "test": { + "$$type": "@backstage/ExtensionInput", + "config": { + "optional": false, + "singleton": false, + }, + "extensionData": [ + [Function], + ], + }, + }, + "kind": "api", + "name": undefined, + "namespace": "test", + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts index dd2130f2cb..16a20b5130 100644 --- a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts @@ -15,7 +15,7 @@ */ import { createExtensionBlueprint } from '../wiring'; import { createApiExtension } from './createApiExtension'; -import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; +import { AnyApiFactory } from '@backstage/core-plugin-api'; export const ApiBlueprint = createExtensionBlueprint({ kind: 'api', @@ -24,27 +24,8 @@ export const ApiBlueprint = createExtensionBlueprint({ dataRefs: { factory: createApiExtension.factoryDataRef, }, - *factory( - params: // remove this form. - | { - api: AnyApiRef; - factory: (params: unknown) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - }, - { config, inputs }, - ) { - yield createApiExtension.factoryDataRef( - typeof params.factory === 'function' - ? params.factory({ config, inputs }) - : params.factory, - ); - }, - namespace: params => { - const apiRef = - 'api' in params ? params.api : (params.factory as { api: AnyApiRef }).api; - - return apiRef.id; + *factory(params: { factory: AnyApiFactory }) { + yield createApiExtension.factoryDataRef(params.factory); }, + namespace: ({ factory }) => factory.api.id, }); diff --git a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx new file mode 100644 index 0000000000..d9c7d296cc --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { AppRootElementBlueprint } from './AppRootElementBlueprint'; + +describe('AppRootElementBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const extension = AppRootElementBlueprint.make({ + params: { + element:
, + }, + }); + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/root", + "input": "elements", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "app-root-element", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts new file mode 100644 index 0000000000..1048d531ab --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { coreExtensionData, createExtensionBlueprint } from '../wiring'; + +export const AppRootElementBlueprint = createExtensionBlueprint({ + kind: 'app-root-element', + attachTo: { id: 'app/root', input: 'elements' }, + output: [coreExtensionData.reactElement], + *factory(params: { element: JSX.Element | (() => JSX.Element) }) { + yield coreExtensionData.reactElement( + typeof params.element === 'function' ? params.element() : params.element, + ); + }, +}); diff --git a/packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx deleted file mode 100644 index 614196fc09..0000000000 --- a/packages/frontend-plugin-api/src/extensions/ComponentBlueprint.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ComponentRef } from '../components'; -import { createExtensionBlueprint } from '../wiring'; -import { createComponentExtension } from './createComponentExtension'; -import { lazy, ComponentType } from 'react'; - -// this is hard to do with blueprints... no TProps for the elements -export const ComponentBlueprint = createExtensionBlueprint({ - kind: 'component', - attachTo: { id: 'app', input: 'components' }, - output: [createComponentExtension.componentDataRef], - dataRefs: { - component: createComponentExtension.componentDataRef, - }, - factory( - { - ref, - loader, - }: { - ref: ComponentRef; - loader: - | { - lazy: (values: any) => Promise>; - } - | { - sync: (values: any) => ComponentType; - }; - }, - { config, inputs }, - ) { - if ('sync' in loader) { - return [ - createComponentExtension.componentDataRef({ - ref, - impl: loader.sync({ config, inputs }), - }), - ]; - } - - const lazyLoader = loader.lazy; - const ExtensionComponent = lazy(() => - lazyLoader({ config, inputs }).then(Component => ({ - default: Component, - })), - ); - - return [ - createComponentExtension.componentDataRef({ - ref, - impl: ExtensionComponent, - }), - ]; - }, -}); diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx index 3abc967fd8..ba4123138f 100644 --- a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx @@ -107,16 +107,18 @@ describe('RouterBlueprint', () => { inputs: { children: createExtensionInput([coreExtensionData.reactElement]), }, - params: { - Component: ({ inputs, children, config }) => ( - -
- {children} -
-
- ), + *factory(originalFactory, { inputs, config }) { + yield* originalFactory({ + Component: ({ children }) => ( + +
+ {children} +
+
+ ), + }); }, }); diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx index bf910eb944..e584a5654c 100644 --- a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx +++ b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ComponentType, PropsWithChildren } from 'react'; +import { ComponentType, PropsWithChildren } from 'react'; import { createExtensionBlueprint } from '../wiring'; import { createRouterExtension } from './createRouterExtension'; @@ -24,25 +24,7 @@ export const RouterBlueprint = createExtensionBlueprint({ dataRefs: { component: createRouterExtension.componentDataRef, }, - *factory( - { - Component, - }: { - Component: ComponentType< - PropsWithChildren<{ - inputs: typeof inputs; - config: typeof config; - }> - >; - }, - { config, inputs }, - ) { - const Wrapper = (props: PropsWithChildren<{}>) => ( - - {props.children} - - ); - - yield createRouterExtension.componentDataRef(Wrapper); + *factory({ Component }: { Component: ComponentType> }) { + yield createRouterExtension.componentDataRef(Component); }, }); diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx index 01ba174efe..fc779f58ab 100644 --- a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx @@ -30,15 +30,12 @@ export const SignInPageBlueprint = createExtensionBlueprint({ { loader, }: { - loader: (opts: { - config: typeof config; - inputs: typeof inputs; - }) => Promise>; + loader: () => Promise>; }, - { config, inputs, node }, + { node }, ) { const ExtensionComponent = lazy(() => - loader({ config, inputs }).then(component => ({ default: component })), + loader().then(component => ({ default: component })), ); yield createSignInPageExtension.componentDataRef(props => ( diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 81137f47e2..f749f7d412 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -312,9 +312,9 @@ class ExtensionBlueprintImpl< > > > { - const optionsSchema = - typeof this.options.config?.schema === 'function' - ? this.options.config?.schema(args.params!) + const optionsSchema = // can remove this args.params check with the split apart of .make + typeof this.options.config?.schema === 'function' && args.params + ? this.options.config?.schema(args.params) : this.options.config?.schema; const schema = { @@ -323,13 +323,13 @@ class ExtensionBlueprintImpl< } as TConfigSchema & TExtensionConfigSchema; const namespace = - typeof this.options.namespace === 'function' - ? this.options.namespace(args.params!) + typeof this.options.namespace === 'function' && args.params + ? this.options.namespace(args.params) : this.options.namespace; const name = - typeof this.options.name === 'function' - ? this.options.name(args.params!) + typeof this.options.name === 'function' && args.params + ? this.options.name(args.params) : this.options.name; return createExtension({ From ab70dc33d7d313729c69f849c0bdae6c2d20abfe Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 9 Aug 2024 08:11:00 +0200 Subject: [PATCH 117/393] chore: implementing the last of the blueprints Signed-off-by: blam --- .../AppRootWrapperBlueprint.test.tsx | 127 ++++++++++++++++++ .../extensions/AppRootWrapperBlueprint.tsx | 36 +++++ 2 files changed, 163 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx new file mode 100644 index 0000000000..b67fc3664d --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { PageBlueprint } from './PageBlueprint'; +import { waitFor } from '@testing-library/react'; +import { + coreExtensionData, + createExtension, + createExtensionInput, +} from '../wiring'; +import { each } from 'lodash'; + +describe('AppRootWrapperBlueprint', () => { + it('should return an extension with sensible defaults', () => { + const extension = AppRootWrapperBlueprint.make({ + params: { + Component: () =>
Hello
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "app/root", + "input": "elements", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "app-root-wrapper", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should render the simple component wrapper', async () => { + const extension = AppRootWrapperBlueprint.make({ + params: { + Component: () =>
Hello
, + }, + }); + + const { getByText } = createExtensionTester( + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
, + }, + }), + ) + .add(extension) + .render(); + + await waitFor(() => expect(getByText('Hello')).toBeInTheDocument()); + }); + + it('should render the complex component wrapper', async () => { + const extension = AppRootWrapperBlueprint.make({ + namespace: 'ns', + name: 'test', + config: { + schema: { + name: z => z.string(), + }, + }, + inputs: { + children: createExtensionInput([coreExtensionData.reactElement]), + }, + *factory(originalFactory, { inputs, config }) { + yield* originalFactory({ + Component: ({ children }) => ( +
+ {children} +
+ ), + }); + }, + }); + + const { getByText, getByTestId } = createExtensionTester( + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
Hi
, + }, + }), + ) + .add(extension, { config: { name: 'Robin' } }) + .add( + createExtension({ + attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
Its Me
)], + }), + ) + .render(); + + await waitFor(() => { + expect(getByText('Its Me')).toBeInTheDocument(); + expect(getByText('Hi')).toBeInTheDocument(); + expect(getByTestId('Robin-1')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx new file mode 100644 index 0000000000..5bcbaa49bd --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ComponentType, PropsWithChildren } from 'react'; +import { createExtensionBlueprint } from '../wiring'; +import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; + +export const AppRootWrapperBlueprint = createExtensionBlueprint({ + kind: 'app-root-wrapper', + attachTo: { id: 'app/root', input: 'elements' }, + output: [createAppRootWrapperExtension.componentDataRef], + dataRefs: { + component: createAppRootWrapperExtension.componentDataRef, + }, + *factory(params: { Component: ComponentType> }) { + // todo(blam): not sure that this wrapping is even necessary anymore. + const Component = (props: PropsWithChildren<{}>) => { + return {props.children}; + }; + + yield createAppRootWrapperExtension.componentDataRef(Component); + }, +}); From 4bc224cc832e8bc9958b585d45b96b0e2bd4d376 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 9 Aug 2024 11:21:16 +0200 Subject: [PATCH 118/393] chore: updating tests and making them work properly Signed-off-by: blam --- .../src/extensions/ApiBlueprint.test.ts | 2 ++ .../AppRootElementBlueprint.test.tsx | 1 + .../AppRootWrapperBlueprint.test.tsx | 4 ++-- .../extensions/AppRootWrapperBlueprint.tsx | 3 ++- .../src/extensions/NavItemBlueprint.test.tsx | 1 + .../src/extensions/NavLogoBlueprint.test.tsx | 1 + .../src/extensions/PageBlueprint.test.tsx | 1 + .../src/extensions/RouterBlueprint.test.tsx | 1 + .../extensions/SignInPageBlueprint.test.tsx | 23 +++++++++++++++---- .../src/extensions/ThemeBlueprint.test.ts | 1 + .../extensions/TranslationBlueprint.test.ts | 1 + 11 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts index c85c021c08..f070956e96 100644 --- a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts @@ -49,6 +49,7 @@ describe('ApiBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } @@ -121,6 +122,7 @@ describe('ApiBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx index d9c7d296cc..3fb8d7b37a 100644 --- a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx @@ -40,6 +40,7 @@ describe('AppRootElementBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx index b67fc3664d..2ff46f0785 100644 --- a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx @@ -23,7 +23,6 @@ import { createExtension, createExtensionInput, } from '../wiring'; -import { each } from 'lodash'; describe('AppRootWrapperBlueprint', () => { it('should return an extension with sensible defaults', () => { @@ -38,7 +37,7 @@ describe('AppRootWrapperBlueprint', () => { "$$type": "@backstage/ExtensionDefinition", "attachTo": { "id": "app/root", - "input": "elements", + "input": "wrappers", }, "configSchema": undefined, "disabled": false, @@ -50,6 +49,7 @@ describe('AppRootWrapperBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx index 5bcbaa49bd..4e8cab8f1b 100644 --- a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { ComponentType, PropsWithChildren } from 'react'; import { createExtensionBlueprint } from '../wiring'; @@ -20,7 +21,7 @@ import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', - attachTo: { id: 'app/root', input: 'elements' }, + attachTo: { id: 'app/root', input: 'wrappers' }, output: [createAppRootWrapperExtension.componentDataRef], dataRefs: { component: createAppRootWrapperExtension.componentDataRef, diff --git a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx index ba7643eee7..90c9cdf9b4 100644 --- a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx @@ -60,6 +60,7 @@ describe('NavItemBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx index fa01862785..4a96475cf5 100644 --- a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx @@ -43,6 +43,7 @@ describe('NavLogoBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx index 5e1d6b7601..c1350b3ddb 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx @@ -76,6 +76,7 @@ describe('PageBlueprint', () => { "toString": [Function], }, ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx index ba4123138f..018c3fc570 100644 --- a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx @@ -52,6 +52,7 @@ describe('RouterBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx index 5ffdd3175b..f97798a64c 100644 --- a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { SignInPageBlueprint } from './SignInPageBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; +import { coreExtensionData, createExtension } from '../wiring'; describe('SignInPageBlueprint', () => { it('should create an extension with sensible defaults', () => { @@ -41,6 +43,7 @@ describe('SignInPageBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } @@ -51,17 +54,29 @@ describe('SignInPageBlueprint', () => { const MockSignInPage = () =>
; const extension = SignInPageBlueprint.make({ + name: 'test', params: { loader: async () => () => }, }); const tester = createExtensionTester(extension); + expect(tester.data(SignInPageBlueprint.dataRefs.component)).toBeDefined(); - const { getByTestId } = tester.render(); + createExtensionTester( + createExtension({ + name: 'dummy', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + element: coreExtensionData.reactElement, + }, + factory: () => ({ element:
}), + }), + ) + .add(extension) + .render(); - // todo(blam): need a better way to test this, currently fails. await waitFor(() => { - expect(getByTestId('mock-sign-in')).toBeInTheDocument(); + expect(screen.getByTestId('mock-sign-in')).toBeInTheDocument(); }); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts index 5bc3a8f7c3..92acd8eeba 100644 --- a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts @@ -44,6 +44,7 @@ describe('ThemeBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts index a059189044..e2a979c097 100644 --- a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts @@ -60,6 +60,7 @@ describe('TranslationBlueprint', () => { "output": [ [Function], ], + "override": [Function], "toString": [Function], "version": "v2", } From 40808de76787f4564ee3fa47d72756b04c61ec8e Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Wed, 5 Jun 2024 17:30:20 +0900 Subject: [PATCH 119/393] Add spec.type to Domain model Signed-off-by: Joe Van Alstyne --- .../examples/domains/artists-domain.yaml | 1 + .../examples/domains/playback-domain.yaml | 1 + .../src/kinds/DomainEntityV1alpha1.test.ts | 11 +++++++++++ .../catalog-model/src/kinds/DomainEntityV1alpha1.ts | 1 + .../src/schema/kinds/Domain.v1alpha1.schema.json | 9 ++++++++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/catalog-model/examples/domains/artists-domain.yaml b/packages/catalog-model/examples/domains/artists-domain.yaml index 598f8500aa..02370d9a14 100644 --- a/packages/catalog-model/examples/domains/artists-domain.yaml +++ b/packages/catalog-model/examples/domains/artists-domain.yaml @@ -13,3 +13,4 @@ metadata: spec: owner: team-a subdomainOf: audio + type: product-group diff --git a/packages/catalog-model/examples/domains/playback-domain.yaml b/packages/catalog-model/examples/domains/playback-domain.yaml index 58b5afe56e..e058ebf0a2 100644 --- a/packages/catalog-model/examples/domains/playback-domain.yaml +++ b/packages/catalog-model/examples/domains/playback-domain.yaml @@ -6,3 +6,4 @@ metadata: spec: owner: user:frank.tiernan subdomainOf: audio + type: product-group diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts index 3c1e7f0a6d..181a696192 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts @@ -32,6 +32,7 @@ describe('DomainV1alpha1Validator', () => { spec: { owner: 'me', subdomainOf: 'parent-domain', + type: 'domain-type', }, }; }); @@ -84,4 +85,14 @@ describe('DomainV1alpha1Validator', () => { (entity as any).spec.subdomainOf = ''; await expect(validator.check(entity)).rejects.toThrow(/subdomainOf/); }); + + it('accepts missing type', async () => { + delete (entity as any).spec.type; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts index 1fe3dfb522..765329abb4 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts @@ -33,6 +33,7 @@ export interface DomainEntityV1alpha1 extends Entity { spec: { owner: string; subdomainOf?: string; + type?: string; }; } diff --git a/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json index 7d0d7e25ff..e4a6c29dd1 100644 --- a/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json +++ b/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json @@ -12,7 +12,8 @@ }, "spec": { "owner": "artist-relations-team", - "subdomainOf": "audio" + "subdomainOf": "audio", + "type": "product-group" } } ], @@ -45,6 +46,12 @@ "description": "An entity reference to another domain of which the domain is a part.", "examples": ["audio"], "minLength": 1 + }, + "type": { + "type": "string", + "description": "The type of domain. There is currently no enforced set of values for this field, so it is left up to the adopting organization to choose a nomenclature that matches their catalog hierarchy.", + "examples": ["product-group", "bundle"], + "minLength": 1 } } } From 218abd5473183912064c5b86cccbd3c92147fe01 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Wed, 5 Jun 2024 17:34:36 +0900 Subject: [PATCH 120/393] Update api-report.md Signed-off-by: Joe Van Alstyne --- packages/catalog-model/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 2d91fa44a5..7b06e80fc5 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -123,6 +123,7 @@ interface DomainEntityV1alpha1 extends Entity { spec: { owner: string; subdomainOf?: string; + type?: string; }; } export { DomainEntityV1alpha1 as DomainEntity }; From 34fa803f2961136a6ab367c8e362f34f86404bc3 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Wed, 5 Jun 2024 17:36:17 +0900 Subject: [PATCH 121/393] Add changeset Signed-off-by: Joe Van Alstyne --- .changeset/young-birds-push.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/young-birds-push.md diff --git a/.changeset/young-birds-push.md b/.changeset/young-birds-push.md new file mode 100644 index 0000000000..5b8ab3a81f --- /dev/null +++ b/.changeset/young-birds-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': minor +--- + +Introduce an optional spec.type attribute on the Domain entity kind From 4e392ebdc49d3afbfa35bd767757bdcf839516d8 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Wed, 5 Jun 2024 17:42:20 +0900 Subject: [PATCH 122/393] Update documentation Signed-off-by: Joe Van Alstyne --- docs/features/software-catalog/descriptor-format.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 416eb27fbd..1287a0780e 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -1303,6 +1303,18 @@ which the domain is a part, e.g. `audio`. This field is optional. | --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | | [`Domain`](#kind-domain) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) | +### `spec.type` [optional] + +The type of domain. There is currently no enforced set of values for this field, +so it is left up to the adopting organization to choose a nomenclature that +matches their catalog hierarchy. This field is optional. + +Some common values for this field could be: + +- `product-area` +- `product-group` +- `bundle` + ## Kind: Location Describes the following entity kind: From ccb514f24d0c0d0b7e0d5d14bc33c94583ff68e4 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Mon, 10 Jun 2024 18:54:32 +0900 Subject: [PATCH 123/393] Add spec.type to Sytem model Signed-off-by: Joe Van Alstyne --- .../systems/artist-engagement-portal-system.yaml | 1 + .../examples/systems/audio-playback-system.yaml | 1 + .../examples/systems/podcast-system.yaml | 1 + .../src/kinds/SystemEntityV1alpha1.test.ts | 11 +++++++++++ .../catalog-model/src/kinds/SystemEntityV1alpha1.ts | 1 + .../src/schema/kinds/System.v1alpha1.schema.json | 9 ++++++++- 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml b/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml index 8de3c00880..17fe21ba69 100644 --- a/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml +++ b/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml @@ -8,3 +8,4 @@ metadata: spec: owner: team-a domain: artists + type: service diff --git a/packages/catalog-model/examples/systems/audio-playback-system.yaml b/packages/catalog-model/examples/systems/audio-playback-system.yaml index 7430ae2ff5..f1edb7f17d 100644 --- a/packages/catalog-model/examples/systems/audio-playback-system.yaml +++ b/packages/catalog-model/examples/systems/audio-playback-system.yaml @@ -6,3 +6,4 @@ metadata: spec: owner: team-c domain: playback + type: feature-set diff --git a/packages/catalog-model/examples/systems/podcast-system.yaml b/packages/catalog-model/examples/systems/podcast-system.yaml index 47a2f7ac9f..77f53925c1 100644 --- a/packages/catalog-model/examples/systems/podcast-system.yaml +++ b/packages/catalog-model/examples/systems/podcast-system.yaml @@ -6,3 +6,4 @@ metadata: spec: owner: team-b domain: playback + type: feature-set diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts index fd78633a89..915f455e79 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts @@ -32,6 +32,7 @@ describe('SystemV1alpha1Validator', () => { spec: { owner: 'me', domain: 'domain', + type: 'system-type', }, }; }); @@ -84,4 +85,14 @@ describe('SystemV1alpha1Validator', () => { (entity as any).spec.domain = ''; await expect(validator.check(entity)).rejects.toThrow(/domain/); }); + + it('accepts missing type', async () => { + delete (entity as any).spec.type; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts index 88d405ec5b..37ecee8f6c 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts @@ -33,6 +33,7 @@ export interface SystemEntityV1alpha1 extends Entity { spec: { owner: string; domain?: string; + type?: string; }; } diff --git a/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json index 2cdbc37076..f91fe751dc 100644 --- a/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json +++ b/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json @@ -12,7 +12,8 @@ }, "spec": { "owner": "artist-relations-team", - "domain": "artists" + "domain": "artists", + "type": "service" } } ], @@ -45,6 +46,12 @@ "description": "An entity reference to the domain that the system belongs to.", "examples": ["artists"], "minLength": 1 + }, + "type": { + "type": "string", + "description": "The type of system. There is currently no enforced set of values for this field, so it is left up to the adopting organization to choose a nomenclature that matches their catalog hierarchy.", + "examples": ["product", "service", "feature-set"], + "minLength": 1 } } } From 2d6003457426d4a9caf8d12df421e9865bb6f482 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Mon, 10 Jun 2024 18:58:00 +0900 Subject: [PATCH 124/393] Update documentation Signed-off-by: Joe Van Alstyne --- docs/features/software-catalog/descriptor-format.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 1287a0780e..57c2283586 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -1243,6 +1243,18 @@ system belongs to, e.g. `artists`. This field is optional. | --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | | [`Domain`](#kind-domain) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) | +### `spec.type` [optional] + +The type of system. There is currently no enforced set of values for this field, +so it is left up to the adopting organization to choose a nomenclature that +matches their catalog hierarchy. This field is optional. + +Some common values for this field could be: + +- `product` +- `service` +- `feature-set` + ## Kind: Domain Describes the following entity kind: From 55a5ef2c3212c2aca2221cc94716f1b18a9d18c2 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Mon, 10 Jun 2024 19:03:17 +0900 Subject: [PATCH 125/393] Update changeset Signed-off-by: Joe Van Alstyne --- .changeset/young-birds-push.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/young-birds-push.md b/.changeset/young-birds-push.md index 5b8ab3a81f..54afa48aaf 100644 --- a/.changeset/young-birds-push.md +++ b/.changeset/young-birds-push.md @@ -2,4 +2,4 @@ '@backstage/catalog-model': minor --- -Introduce an optional spec.type attribute on the Domain entity kind +Introduce an optional spec.type attribute on the Domain and System entity kinds From ef71b00bbe0b2878379c73d67747751961e7cba2 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Mon, 10 Jun 2024 19:09:37 +0900 Subject: [PATCH 126/393] Update api-report.md Signed-off-by: Joe Van Alstyne --- packages/catalog-model/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 7b06e80fc5..de941e2502 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -455,6 +455,7 @@ interface SystemEntityV1alpha1 extends Entity { spec: { owner: string; domain?: string; + type?: string; }; } export { SystemEntityV1alpha1 as SystemEntity }; From 1a0d955bb526e47ef8d635e267adb56868207e25 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Fri, 9 Aug 2024 18:17:19 +0900 Subject: [PATCH 127/393] Add test to DomainEntityV1alpha1.test.ts Signed-off-by: Joe Van Alstyne --- .../catalog-model/src/kinds/DomainEntityV1alpha1.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts index 181a696192..ce35441fc8 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts @@ -95,4 +95,9 @@ describe('DomainV1alpha1Validator', () => { (entity as any).spec.type = ''; await expect(validator.check(entity)).rejects.toThrow(/type/); }); + + it('rejects wrong type', async () => { + (entity as any).spec.type = 7; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); From 33489f73abe9032304c37c5777fbb5e91bba6b53 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Fri, 9 Aug 2024 18:17:33 +0900 Subject: [PATCH 128/393] Add test to SystemEntityV1alpha1.test.ts Signed-off-by: Joe Van Alstyne --- .../catalog-model/src/kinds/SystemEntityV1alpha1.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts index 915f455e79..6ea295f190 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts @@ -95,4 +95,9 @@ describe('SystemV1alpha1Validator', () => { (entity as any).spec.type = ''; await expect(validator.check(entity)).rejects.toThrow(/type/); }); + + it('rejects wrong type', async () => { + (entity as any).spec.type = 7; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); From 30e13a6b822d0c9db7bec55411b0e7cb43d9b9c6 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Fri, 9 Aug 2024 18:25:16 +0900 Subject: [PATCH 129/393] Fix test order in DomainEntityV1alpha1.test.ts, SystemEntityV1alpha1.test.ts Signed-off-by: Joe Van Alstyne --- .../src/kinds/DomainEntityV1alpha1.test.ts | 10 +++++----- .../src/kinds/SystemEntityV1alpha1.test.ts | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts index ce35441fc8..61408b7e80 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts @@ -91,13 +91,13 @@ describe('DomainV1alpha1Validator', () => { await expect(validator.check(entity)).resolves.toBe(true); }); - it('rejects empty type', async () => { - (entity as any).spec.type = ''; - await expect(validator.check(entity)).rejects.toThrow(/type/); - }); - it('rejects wrong type', async () => { (entity as any).spec.type = 7; await expect(validator.check(entity)).rejects.toThrow(/type/); }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts index 6ea295f190..828fe7a648 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts @@ -91,13 +91,13 @@ describe('SystemV1alpha1Validator', () => { await expect(validator.check(entity)).resolves.toBe(true); }); - it('rejects empty type', async () => { - (entity as any).spec.type = ''; - await expect(validator.check(entity)).rejects.toThrow(/type/); - }); - it('rejects wrong type', async () => { (entity as any).spec.type = 7; await expect(validator.check(entity)).rejects.toThrow(/type/); }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); From 5394a94efa772bfba3ded5e79528427529cf9bf0 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Tue, 4 Jun 2024 17:09:45 +0900 Subject: [PATCH 130/393] Add columns prop to ConsumingComponentsCard and ProvidingComponentsCard Signed-off-by: Joe Van Alstyne --- .../components/ComponentsCards/ConsumingComponentsCard.tsx | 7 +++++-- .../components/ComponentsCards/ProvidingComponentsCard.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 5feb1c96e4..8e461bb08a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -31,6 +31,7 @@ import { InfoCardVariants, Link, Progress, + TableColumn, WarningPanel, } from '@backstage/core-components'; @@ -39,8 +40,10 @@ import { */ export const ConsumingComponentsCard = (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => { - const { variant = 'gridItem' } = props; + const { variant = 'gridItem', columns = EntityTable.componentEntityColumns } = + props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_API_CONSUMED_BY, @@ -82,7 +85,7 @@ export const ConsumingComponentsCard = (props: {
} - columns={EntityTable.componentEntityColumns} + columns={columns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index ef7f2e1071..33dc7217d4 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -31,14 +31,17 @@ import { InfoCardVariants, Link, Progress, + TableColumn, WarningPanel, } from '@backstage/core-components'; /** @public */ export const ProvidingComponentsCard = (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => { - const { variant = 'gridItem' } = props; + const { variant = 'gridItem', columns = EntityTable.componentEntityColumns } = + props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_API_PROVIDED_BY, @@ -80,7 +83,7 @@ export const ProvidingComponentsCard = (props: {
} - columns={EntityTable.componentEntityColumns} + columns={columns} entities={entities as ComponentEntity[]} /> ); From 770ba02d32c0ca365b360b516ddbd298142b55f7 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Tue, 4 Jun 2024 17:17:21 +0900 Subject: [PATCH 131/393] Add changeset Signed-off-by: Joe Van Alstyne --- .changeset/forty-geckos-change.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/forty-geckos-change.md diff --git a/.changeset/forty-geckos-change.md b/.changeset/forty-geckos-change.md new file mode 100644 index 0000000000..e7ac112f57 --- /dev/null +++ b/.changeset/forty-geckos-change.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Custom columns can now be optionally provided to ConsumingComponentsCard and ProvidingComponentsCard From 74c0e7712e803c8fcdf22ab59a725a22394e3dbe Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Tue, 4 Jun 2024 17:20:09 +0900 Subject: [PATCH 132/393] Update api-report.md Signed-off-by: Joe Van Alstyne --- plugins/api-docs/api-report.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 2fc852777b..65cc421b78 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -9,6 +9,7 @@ import { ApiEntity } from '@backstage/catalog-model'; import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogTableRow } from '@backstage/plugin-catalog'; +import { ComponentEntity } from '@backstage/catalog-model'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; @@ -94,6 +95,7 @@ export const ConsumedApisCard: (props: { // @public (undocumented) export const ConsumingComponentsCard: (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => React_2.JSX.Element; // @public @@ -124,6 +126,7 @@ export const EntityConsumedApisCard: (props: { // @public (undocumented) export const EntityConsumingComponentsCard: (props: { variant?: InfoCardVariants | undefined; + columns?: TableColumn[] | undefined; }) => JSX_2.Element; // @public (undocumented) @@ -141,6 +144,7 @@ export const EntityProvidedApisCard: (props: { // @public (undocumented) export const EntityProvidingComponentsCard: (props: { variant?: InfoCardVariants | undefined; + columns?: TableColumn[] | undefined; }) => JSX_2.Element; // @public (undocumented) @@ -191,6 +195,7 @@ export const ProvidedApisCard: (props: { // @public (undocumented) export const ProvidingComponentsCard: (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => React_2.JSX.Element; // @public (undocumented) From 2881a03ebb35cb9e2becf199b90f252d0dedef59 Mon Sep 17 00:00:00 2001 From: Joe Van Alstyne Date: Tue, 4 Jun 2024 17:32:31 +0900 Subject: [PATCH 133/393] Update changeset Signed-off-by: Joe Van Alstyne --- .changeset/forty-geckos-change.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/forty-geckos-change.md b/.changeset/forty-geckos-change.md index e7ac112f57..484a485670 100644 --- a/.changeset/forty-geckos-change.md +++ b/.changeset/forty-geckos-change.md @@ -2,4 +2,4 @@ '@backstage/plugin-api-docs': patch --- -Custom columns can now be optionally provided to ConsumingComponentsCard and ProvidingComponentsCard +`ConsumingComponentsCard` and `ProvidingComponentsCard` will now optionally accept `columns` to override which table columns are displayed From e6c763f76b6d19865a320c86a361fbb3ef5035f9 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 9 Aug 2024 15:12:47 +0200 Subject: [PATCH 134/393] chore: getting the tests working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 410 +++++++++++++++--- .../ApiBlueprint.test.ts | 3 +- .../ApiBlueprint.ts | 4 +- .../AppRootElementBlueprint.test.tsx | 0 .../AppRootElementBlueprint.ts | 1 + .../AppRootWrapperBlueprint.test.tsx | 7 +- .../AppRootWrapperBlueprint.tsx | 3 +- .../IconBundleBlueprint.ts | 0 .../NavItemBlueprint.test.tsx | 1 - .../NavItemBlueprint.ts | 12 +- .../NavLogoBlueprint.test.tsx | 0 .../NavLogoBlueprint.ts | 3 +- .../PageBlueprint.test.tsx | 2 +- .../PageBlueprint.tsx | 5 +- .../RouterBlueprint.test.tsx | 2 +- .../RouterBlueprint.tsx | 3 +- .../SignInPageBlueprint.test.tsx | 0 .../SignInPageBlueprint.tsx | 3 +- .../ThemeBlueprint.test.ts | 3 +- .../ThemeBlueprint.ts | 4 +- .../TranslationBlueprint.test.ts | 2 +- .../TranslationBlueprint.ts | 4 +- .../src/blueprints/index.ts | 27 ++ .../src/extensions/createApiExtension.ts | 5 +- .../createAppRootElementExtension.ts | 1 + .../createAppRootWrapperExtension.tsx | 1 + .../src/extensions/createNavItemExtension.tsx | 2 + .../src/extensions/createNavLogoExtension.tsx | 2 + .../src/extensions/createPageExtension.tsx | 1 + .../src/extensions/createRouterExtension.tsx | 1 + .../extensions/createSignInPageExtension.tsx | 1 + .../src/extensions/createThemeExtension.ts | 10 +- .../extensions/createTranslationExtension.ts | 10 +- .../src/extensions/index.ts | 1 - packages/frontend-plugin-api/src/index.ts | 1 + .../wiring/createExtensionBlueprint.test.tsx | 95 +--- .../src/wiring/createExtensionBlueprint.ts | 224 +++++----- .../src/app/createExtensionTester.tsx | 5 +- 38 files changed, 578 insertions(+), 281 deletions(-) rename packages/frontend-plugin-api/src/{extensions => blueprints}/ApiBlueprint.test.ts (97%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/ApiBlueprint.ts (91%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/AppRootElementBlueprint.test.tsx (100%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/AppRootElementBlueprint.ts (98%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/AppRootWrapperBlueprint.test.tsx (95%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/AppRootWrapperBlueprint.tsx (92%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/IconBundleBlueprint.ts (100%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/NavItemBlueprint.test.tsx (98%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/NavItemBlueprint.ts (86%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/NavLogoBlueprint.test.tsx (100%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/NavLogoBlueprint.ts (92%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/PageBlueprint.test.tsx (98%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/PageBlueprint.tsx (97%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/RouterBlueprint.test.tsx (98%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/RouterBlueprint.tsx (92%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/SignInPageBlueprint.test.tsx (100%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/SignInPageBlueprint.tsx (93%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/ThemeBlueprint.test.ts (94%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/ThemeBlueprint.ts (91%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/TranslationBlueprint.test.ts (98%) rename packages/frontend-plugin-api/src/{extensions => blueprints}/TranslationBlueprint.ts (90%) create mode 100644 packages/frontend-plugin-api/src/blueprints/index.ts diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index a8f8ce591b..2d3f1d3278 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -189,6 +189,27 @@ export type AnyRoutes = { [name in string]: RouteRef | SubRouteRef; }; +// @public (undocumented) +export const ApiBlueprint: ExtensionBlueprint< + 'api', + undefined, + undefined, + { + factory: AnyApiFactory; + }, + ConfigurableExtensionDataRef, + {}, + {}, + {}, + { + factory: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + } +>; + export { ApiFactory }; export { ApiHolder }; @@ -240,6 +261,50 @@ export interface AppNodeSpec { readonly source?: BackstagePlugin; } +// @public (undocumented) +export const AppRootElementBlueprint: ExtensionBlueprint< + 'app-root-element', + undefined, + undefined, + { + element: JSX.Element | (() => JSX.Element); + }, + ConfigurableExtensionDataRef, + {}, + {}, + {}, + never +>; + +// @public (undocumented) +export const AppRootWrapperBlueprint: ExtensionBlueprint< + 'app-root-wrapper', + undefined, + undefined, + { + Component: ComponentType>; + }, + ConfigurableExtensionDataRef< + React_2.ComponentType<{ + children?: React_2.ReactNode; + }>, + 'app.root.wrapper', + {} + >, + {}, + {}, + {}, + { + component: ConfigurableExtensionDataRef< + React_2.ComponentType<{ + children?: React_2.ReactNode; + }>, + 'app.root.wrapper', + {} + >; + } +>; + export { AppTheme }; export { AppThemeApi }; @@ -381,7 +446,7 @@ export type CoreNotFoundErrorPageProps = { // @public (undocumented) export type CoreProgressProps = {}; -// @public (undocumented) +// @public @deprecated (undocumented) export function createApiExtension< TConfig extends {}, TInputs extends AnyExtensionInputMap, @@ -425,7 +490,7 @@ export { createApiFactory }; export { createApiRef }; -// @public +// @public @deprecated export function createAppRootElementExtension< TConfig extends {}, TInputs extends AnyExtensionInputMap, @@ -447,7 +512,7 @@ export function createAppRootElementExtension< }) => JSX_2.Element); }): ExtensionDefinition; -// @public +// @public @deprecated export function createAppRootWrapperExtension< TConfig extends {}, TInputs extends AnyExtensionInputMap, @@ -675,7 +740,7 @@ export type CreateExtensionBlueprintOptions< }, > = { kind: TKind; - namespace?: TNamespace | ((params: TParams) => TNamespace); + namespace?: TNamespace; attachTo: { id: string; input: string; @@ -683,9 +748,9 @@ export type CreateExtensionBlueprintOptions< disabled?: boolean; inputs?: TInputs; output: Array; - name?: TName | ((params: TParams) => TName); + name?: TName; config?: { - schema: TConfigSchema | ((params: TParams) => TConfigSchema); + schema: TConfigSchema; }; factory( params: TParams, @@ -824,7 +889,7 @@ export function createExternalRouteRef< } >; -// @public +// @public @deprecated export function createNavItemExtension(options: { namespace?: string; name?: string; @@ -859,7 +924,7 @@ export namespace createNavItemExtension { >; } -// @public +// @public @deprecated export function createNavLogoExtension(options: { name?: string; namespace?: string; @@ -888,7 +953,7 @@ export namespace createNavLogoExtension { >; } -// @public +// @public @deprecated export function createPageExtension< TConfig extends { path: string; @@ -958,7 +1023,7 @@ export function createRouteRef< } >; -// @public +// @public @deprecated export function createRouterExtension< TConfig extends {}, TInputs extends AnyExtensionInputMap, @@ -997,7 +1062,7 @@ export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, ): PortableSchema; -// @public (undocumented) +// @public @deprecated (undocumented) export function createSignInPageExtension< TConfig extends {}, TInputs extends AnyExtensionInputMap, @@ -1036,7 +1101,7 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; -// @public (undocumented) +// @public @deprecated (undocumented) export function createThemeExtension( theme: AppTheme, ): ExtensionDefinition< @@ -1049,7 +1114,7 @@ export function createThemeExtension( string | undefined >; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createThemeExtension { const // (undocumented) themeDataRef: ConfigurableExtensionDataRef< @@ -1059,7 +1124,7 @@ export namespace createThemeExtension { >; } -// @public (undocumented) +// @public @deprecated (undocumented) export function createTranslationExtension(options: { name?: string; resource: TranslationResource | TranslationMessages; @@ -1073,7 +1138,7 @@ export function createTranslationExtension(options: { string | undefined >; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createTranslationExtension { const // (undocumented) translationDataRef: ConfigurableExtensionDataRef< @@ -1153,9 +1218,31 @@ export interface ExtensionBlueprint< > { // (undocumented) dataRefs: TDataRefs; + // (undocumented) make< TNewNamespace extends string | undefined, TNewName extends string | undefined, + >(args: { + namespace?: TNewNamespace; + name?: TNewName; + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + params: TParams; + }): ExtensionDefinition< + TConfig, + TConfigInput, + UOutput, + TInputs, + TKind, + string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, + string | undefined extends TNewName ? TName : TNewName + >; + makeWithOverrides< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, @@ -1170,55 +1257,48 @@ export interface ExtensionBlueprint< } >; }, - >( - args: { - namespace?: TNewNamespace; - name?: TNewName; - attachTo?: { - id: string; - input: string; + >(args: { + namespace?: TNewNamespace; + name?: TNewName; + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; }; - disabled?: boolean; - inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & - string}' is already defined in parent definition`; - }; - output?: Array; - config?: { - schema: TExtensionConfigSchema & { - [KName in keyof TConfig]?: `Error: Config key '${KName & - string}' is already defined in parent schema`; + }; + factory( + originalFactory: ( + params: TParams, + context?: { + config?: TConfig; + inputs?: Expand>; + }, + ) => ExtensionDataContainer, + context: { + node: AppNode; + config: TConfig & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; }; - }; - } & ( - | ({ - factory( - originalFactory: ( - params: TParams, - context?: { - config?: TConfig; - inputs?: Expand>; - }, - ) => ExtensionDataContainer, - context: { - node: AppNode; - config: TConfig & { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }; - inputs: Expand>; - }, - ): Iterable; - } & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - UFactoryOutput - >) - | { - params: TParams; - } - ), - ): ExtensionDefinition< + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + UFactoryOutput + >; + }): ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType @@ -1616,6 +1696,73 @@ export interface LegacyExtensionInput< export { microsoftAuthApiRef }; +// @public (undocumented) +export const NavItemBlueprint: ExtensionBlueprint< + 'nav-item', + undefined, + undefined, + { + title: string; + icon: IconComponent_2; + routeRef: RouteRef; + }, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent_2; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + {}, + {}, + { + target: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent_2; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + } +>; + +// @public (undocumented) +export const NavLogoBlueprint: ExtensionBlueprint< + 'nav-logo', + undefined, + undefined, + { + logoIcon: JSX.Element; + logoFull: JSX.Element; + }, + ConfigurableExtensionDataRef< + { + logoIcon?: JSX.Element | undefined; + logoFull?: JSX.Element | undefined; + }, + 'core.nav-logo.logo-elements', + {} + >, + {}, + {}, + {}, + { + logoElements: ConfigurableExtensionDataRef< + { + logoIcon?: JSX.Element | undefined; + logoFull?: JSX.Element | undefined; + }, + 'core.nav-logo.logo-elements', + {} + >; + } +>; + export { OAuthApi }; export { OAuthRequestApi }; @@ -1634,6 +1781,35 @@ export { oneloginAuthApiRef }; export { OpenIdConnectApi }; +// @public (undocumented) +export const PageBlueprint: ExtensionBlueprint< + 'page', + undefined, + undefined, + { + defaultPath: string; + loader: () => Promise; + routeRef?: RouteRef | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + RouteRef & { + optional: true; + } + >, + {}, + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + never +>; + export { PendingOAuthRequest }; // @public (undocumented) @@ -1699,6 +1875,35 @@ export type RouteFunc = ( : readonly [params: TParams] ) => string; +// @public (undocumented) +export const RouterBlueprint: ExtensionBlueprint< + 'app-router-component', + undefined, + undefined, + { + Component: ComponentType>; + }, + ConfigurableExtensionDataRef< + ComponentType<{ + children?: ReactNode; + }>, + 'app.router.wrapper', + {} + >, + {}, + {}, + {}, + { + component: ConfigurableExtensionDataRef< + ComponentType<{ + children?: ReactNode; + }>, + 'app.router.wrapper', + {} + >; + } +>; + // @public export interface RouteRef< TParams extends AnyRouteRefParams = AnyRouteRefParams, @@ -1733,6 +1938,31 @@ export { SessionApi }; export { SessionState }; +// @public (undocumented) +export const SignInPageBlueprint: ExtensionBlueprint< + 'sign-in-page', + undefined, + undefined, + { + loader: () => Promise>; + }, + ConfigurableExtensionDataRef< + React_2.ComponentType, + 'core.sign-in-page.component', + {} + >, + {}, + {}, + {}, + { + component: ConfigurableExtensionDataRef< + React_2.ComponentType, + 'core.sign-in-page.component', + {} + >; + } +>; + export { StorageApi }; export { storageApiRef }; @@ -1751,6 +1981,62 @@ export interface SubRouteRef< readonly T: TParams; } +// @public (undocumented) +export const ThemeBlueprint: ExtensionBlueprint< + 'theme', + 'app', + undefined, + { + theme: AppTheme; + }, + ConfigurableExtensionDataRef, + {}, + {}, + {}, + { + theme: ConfigurableExtensionDataRef; + } +>; + +// @public (undocumented) +export const TranslationBlueprint: ExtensionBlueprint< + 'translation', + undefined, + undefined, + { + resource: TranslationResource | TranslationMessages; + }, + ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >, + {}, + {}, + {}, + { + translation: ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >; + } +>; + export { TranslationMessages }; export { TranslationMessagesOptions }; diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts similarity index 97% rename from packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts rename to packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index f070956e96..3b865a78bb 100644 --- a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -30,6 +30,7 @@ describe('ApiBlueprint', () => { params: { factory, }, + namespace: 'test', }); expect(extension).toMatchInlineSnapshot(` @@ -60,7 +61,7 @@ describe('ApiBlueprint', () => { const api = createApiRef<{ foo: string }>({ id: 'test' }); const factory = jest.fn(() => ({ foo: 'bar' })); - const extension = ApiBlueprint.make({ + const extension = ApiBlueprint.makeWithOverrides({ config: { schema: { test: z => z.string().default('test'), diff --git a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts similarity index 91% rename from packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts index 16a20b5130..da12faef1a 100644 --- a/packages/frontend-plugin-api/src/extensions/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts @@ -14,9 +14,10 @@ * limitations under the License. */ import { createExtensionBlueprint } from '../wiring'; -import { createApiExtension } from './createApiExtension'; +import { createApiExtension } from '../extensions/createApiExtension'; import { AnyApiFactory } from '@backstage/core-plugin-api'; +/** @public */ export const ApiBlueprint = createExtensionBlueprint({ kind: 'api', attachTo: { id: 'app', input: 'apis' }, @@ -27,5 +28,4 @@ export const ApiBlueprint = createExtensionBlueprint({ *factory(params: { factory: AnyApiFactory }) { yield createApiExtension.factoryDataRef(params.factory); }, - namespace: ({ factory }) => factory.api.id, }); diff --git a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx similarity index 100% rename from packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx diff --git a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts similarity index 98% rename from packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts index 1048d531ab..e44942979d 100644 --- a/packages/frontend-plugin-api/src/extensions/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts @@ -15,6 +15,7 @@ */ import { coreExtensionData, createExtensionBlueprint } from '../wiring'; +/** @public */ export const AppRootElementBlueprint = createExtensionBlueprint({ kind: 'app-root-element', attachTo: { id: 'app/root', input: 'elements' }, diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx similarity index 95% rename from packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx index 2ff46f0785..f7feab1d46 100644 --- a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -78,7 +78,7 @@ describe('AppRootWrapperBlueprint', () => { }); it('should render the complex component wrapper', async () => { - const extension = AppRootWrapperBlueprint.make({ + const extension = AppRootWrapperBlueprint.makeWithOverrides({ namespace: 'ns', name: 'test', config: { @@ -94,6 +94,9 @@ describe('AppRootWrapperBlueprint', () => { Component: ({ children }) => (
{children} + {inputs.children.flatMap(c => + c.get(coreExtensionData.reactElement), + )}
), }); @@ -119,9 +122,9 @@ describe('AppRootWrapperBlueprint', () => { .render(); await waitFor(() => { - expect(getByText('Its Me')).toBeInTheDocument(); expect(getByText('Hi')).toBeInTheDocument(); expect(getByTestId('Robin-1')).toBeInTheDocument(); + expect(getByText('Its Me')).toBeInTheDocument(); }); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx similarity index 92% rename from packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx rename to packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index 4e8cab8f1b..db63550880 100644 --- a/packages/frontend-plugin-api/src/extensions/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -17,8 +17,9 @@ import React from 'react'; import { ComponentType, PropsWithChildren } from 'react'; import { createExtensionBlueprint } from '../wiring'; -import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; +import { createAppRootWrapperExtension } from '../extensions/createAppRootWrapperExtension'; +/** @public */ export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', attachTo: { id: 'app/root', input: 'wrappers' }, diff --git a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts similarity index 100% rename from packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts diff --git a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx similarity index 98% rename from packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx index 90c9cdf9b4..94b9e7b8e6 100644 --- a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx @@ -44,7 +44,6 @@ describe('NavItemBlueprint', () => { "additionalProperties": false, "properties": { "title": { - "default": "TEST", "type": "string", }, }, diff --git a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts similarity index 86% rename from packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 568431c0f5..5391d0dec2 100644 --- a/packages/frontend-plugin-api/src/extensions/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -17,8 +17,9 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { RouteRef } from '../routing'; import { createExtensionBlueprint } from '../wiring'; -import { createNavItemExtension } from './createNavItemExtension'; +import { createNavItemExtension } from '../extensions/createNavItemExtension'; +/** @public */ export const NavItemBlueprint = createExtensionBlueprint({ kind: 'nav-item', attachTo: { id: 'app/nav', input: 'items' }, @@ -30,6 +31,7 @@ export const NavItemBlueprint = createExtensionBlueprint({ { icon, routeRef, + title, }: { title: string; icon: IconComponent; @@ -38,14 +40,14 @@ export const NavItemBlueprint = createExtensionBlueprint({ { config }, ) => [ createNavItemExtension.targetDataRef({ - title: config.title, + title: config.title ?? title, icon, routeRef, }), ], config: { - schema: ({ title }) => ({ - title: z => z.string().default(title), - }), + schema: { + title: z => z.string().optional(), + }, }, }); diff --git a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx similarity index 100% rename from packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx diff --git a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts similarity index 92% rename from packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts index d119287f29..f31b3cec9b 100644 --- a/packages/frontend-plugin-api/src/extensions/NavLogoBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts @@ -15,8 +15,9 @@ */ import { createExtensionBlueprint } from '../wiring'; -import { createNavLogoExtension } from './createNavLogoExtension'; +import { createNavLogoExtension } from '../extensions/createNavLogoExtension'; +/** @public */ export const NavLogoBlueprint = createExtensionBlueprint({ kind: 'nav-logo', attachTo: { id: 'app/nav', input: 'logos' }, diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx similarity index 98% rename from packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index c1350b3ddb..c4bcb4b467 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -106,7 +106,7 @@ describe('PageBlueprint', () => { }); it('should allow defining additional inputs to the extension', async () => { - const myPage = PageBlueprint.make({ + const myPage = PageBlueprint.makeWithOverrides({ name: 'test-page', inputs: { cards: createExtensionInput([coreExtensionData.reactElement], { diff --git a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx similarity index 97% rename from packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx rename to packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index 506301ffdf..bfcf4a7934 100644 --- a/packages/frontend-plugin-api/src/extensions/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -18,6 +18,7 @@ import { RouteRef } from '../routing'; import { coreExtensionData, createExtensionBlueprint } from '../wiring'; import { ExtensionBoundary } from '../components'; +/** @public */ export const PageBlueprint = createExtensionBlueprint({ kind: 'page', attachTo: { id: 'app/routes', input: 'routes' }, @@ -37,7 +38,7 @@ export const PageBlueprint = createExtensionBlueprint({ loader, routeRef, }: { - defaultPath?: string; + defaultPath: string; loader: () => Promise; routeRef?: RouteRef; }, @@ -47,7 +48,7 @@ export const PageBlueprint = createExtensionBlueprint({ loader().then(element => ({ default: () => element })), ); - yield coreExtensionData.routePath(config.path ?? defaultPath!); + yield coreExtensionData.routePath(config.path ?? defaultPath); yield coreExtensionData.reactElement( diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx similarity index 98% rename from packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx index 018c3fc570..fe40aaa8d7 100644 --- a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx @@ -97,7 +97,7 @@ describe('RouterBlueprint', () => { }); it('should work with complex options and props', async () => { - const extension = RouterBlueprint.make({ + const extension = RouterBlueprint.makeWithOverrides({ namespace: 'test', name: 'test', config: { diff --git a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx similarity index 92% rename from packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx rename to packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx index e584a5654c..24ee805659 100644 --- a/packages/frontend-plugin-api/src/extensions/RouterBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx @@ -15,8 +15,9 @@ */ import { ComponentType, PropsWithChildren } from 'react'; import { createExtensionBlueprint } from '../wiring'; -import { createRouterExtension } from './createRouterExtension'; +import { createRouterExtension } from '../extensions/createRouterExtension'; +/** @public */ export const RouterBlueprint = createExtensionBlueprint({ kind: 'app-router-component', attachTo: { id: 'app/root', input: 'router' }, diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx similarity index 100% rename from packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx diff --git a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx similarity index 93% rename from packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx rename to packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index fc779f58ab..2727335d8a 100644 --- a/packages/frontend-plugin-api/src/extensions/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -15,10 +15,11 @@ */ import React, { ComponentType, lazy } from 'react'; import { createExtensionBlueprint } from '../wiring'; -import { createSignInPageExtension } from './createSignInPageExtension'; +import { createSignInPageExtension } from '../extensions/createSignInPageExtension'; import { SignInPageProps } from '@backstage/core-plugin-api'; import { ExtensionBoundary } from '../components'; +/** @public */ export const SignInPageBlueprint = createExtensionBlueprint({ kind: 'sign-in-page', attachTo: { id: 'app/root', input: 'signInPage' }, diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts similarity index 94% rename from packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts rename to packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts index 92acd8eeba..40323b6a31 100644 --- a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts @@ -27,7 +27,8 @@ describe('ThemeBlueprint', () => { } as AppTheme; it('should create an extension with sensible defaults', () => { - expect(ThemeBlueprint.make({ params: { theme } })).toMatchInlineSnapshot(` + expect(ThemeBlueprint.make({ name: 'light', params: { theme } })) + .toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", "attachTo": { diff --git a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts similarity index 91% rename from packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index 500946713b..cd97e0437f 100644 --- a/packages/frontend-plugin-api/src/extensions/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -16,12 +16,12 @@ import { AppTheme } from '@backstage/core-plugin-api'; import { createExtensionBlueprint } from '../wiring'; -import { createThemeExtension } from './createThemeExtension'; +import { createThemeExtension } from '../extensions/createThemeExtension'; +/** @public */ export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', namespace: 'app', - name: ({ theme }) => theme.id, attachTo: { id: 'app', input: 'themes' }, output: [createThemeExtension.themeDataRef], dataRefs: { diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts similarity index 98% rename from packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts rename to packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts index e2a979c097..dfb918053a 100644 --- a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts @@ -56,7 +56,7 @@ describe('TranslationBlueprint', () => { "inputs": {}, "kind": "translation", "name": "blob", - "namespace": "translationRefId", + "namespace": undefined, "output": [ [Function], ], diff --git a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts similarity index 90% rename from packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts index 581f4d176a..c70ca56f6b 100644 --- a/packages/frontend-plugin-api/src/extensions/TranslationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts @@ -15,12 +15,12 @@ */ import { createExtensionBlueprint } from '../wiring'; -import { createTranslationExtension } from './createTranslationExtension'; +import { createTranslationExtension } from '../extensions/createTranslationExtension'; import { TranslationMessages, TranslationResource } from '../translation'; +/** @public */ export const TranslationBlueprint = createExtensionBlueprint({ kind: 'translation', - namespace: ({ resource }) => resource.id, attachTo: { id: 'app', input: 'translations' }, output: [createTranslationExtension.translationDataRef], dataRefs: { diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts new file mode 100644 index 0000000000..85f7a99a2c --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ApiBlueprint } from './ApiBlueprint'; +export { AppRootElementBlueprint } from './AppRootElementBlueprint'; +export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; +export { IconBundleBlueprint } from './IconBundleBlueprint'; +export { NavItemBlueprint } from './NavItemBlueprint'; +export { NavLogoBlueprint } from './NavLogoBlueprint'; +export { PageBlueprint } from './PageBlueprint'; +export { RouterBlueprint } from './RouterBlueprint'; +export { SignInPageBlueprint } from './SignInPageBlueprint'; +export { ThemeBlueprint } from './ThemeBlueprint'; +export { TranslationBlueprint } from './TranslationBlueprint'; diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index 93346f1ec9..b1db2a0106 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -24,7 +24,10 @@ import { import { AnyExtensionInputMap } from '../wiring/createExtension'; import { Expand } from '../types'; -/** @public */ +/** + * @public + * @deprecated Use {@link ApiBlueprint} instead. + */ export function createApiExtension< TConfig extends {}, TInputs extends AnyExtensionInputMap, diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts index 8be82d5540..c8f7bdc366 100644 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts @@ -30,6 +30,7 @@ import { * the app layout. This is useful for example for shared popups and similar. * * @public + * @deprecated Use {@link AppRootElementBlueprint} instead. */ export function createAppRootElementExtension< TConfig extends {}, diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx index ce2eb89f25..4f70f29470 100644 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx @@ -31,6 +31,7 @@ import { Expand } from '../types'; * and similar. * * @public + * @deprecated Use {@link AppRootWrapperBlueprint} instead. */ export function createAppRootWrapperExtension< TConfig extends {}, diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx index 2a2c47835a..d694440427 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx @@ -21,7 +21,9 @@ import { RouteRef } from '../routing'; /** * Helper for creating extensions for a nav item. + * * @public + * @deprecated Use {@link NavItemBlueprint} instead. */ export function createNavItemExtension(options: { namespace?: string; diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx index 1f2419bd02..02c32e7f34 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx @@ -18,7 +18,9 @@ import { createExtension, createExtensionDataRef } from '../wiring'; /** * Helper for creating extensions for a nav logos. + * * @public + * @deprecated Use {@link NavLogoBlueprint} instead. */ export function createNavLogoExtension(options: { name?: string; diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index cd434a3ffc..bef642e465 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -31,6 +31,7 @@ import { ExtensionDefinition } from '../wiring/createExtension'; * Helper for creating extensions for a routable React page component. * * @public + * @deprecated Use {@link PageBlueprint} instead. */ export function createPageExtension< TConfig extends { path: string }, diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx index 12f946e8a7..943711f5e0 100644 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx @@ -31,6 +31,7 @@ import { Expand } from '../types'; * MemoryRouter in tests, or to add additional props to a BrowserRouter. * * @public + * @deprecated Use {@link RouterBlueprint} instead. */ export function createRouterExtension< TConfig extends {}, diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx index 312d30c78c..3f41202f11 100644 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx @@ -30,6 +30,7 @@ import { SignInPageProps } from '@backstage/core-plugin-api'; /** * * @public + * @deprecated Use {@link SignInPageBlueprint} instead. */ export function createSignInPageExtension< TConfig extends {}, diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts index 1740f05a5a..e523fd9c05 100644 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts @@ -17,7 +17,10 @@ import { createExtension, createExtensionDataRef } from '../wiring'; import { AppTheme } from '@backstage/core-plugin-api'; -/** @public */ +/** + * @public + * @deprecated Use {@link ThemeBlueprint} instead. + */ export function createThemeExtension(theme: AppTheme) { return createExtension({ kind: 'theme', @@ -31,7 +34,10 @@ export function createThemeExtension(theme: AppTheme) { }); } -/** @public */ +/** + * @public + * @deprecated Use {@link ThemeBlueprint} instead. + */ export namespace createThemeExtension { export const themeDataRef = createExtensionDataRef().with({ id: 'core.theme.theme', diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts index 9445e32e24..c4d2f6037d 100644 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts @@ -17,7 +17,10 @@ import { TranslationMessages, TranslationResource } from '../translation'; import { createExtension, createExtensionDataRef } from '../wiring'; -/** @public */ +/** + * @public + * @deprecated Use {@link TranslationBlueprint} instead. + */ export function createTranslationExtension(options: { name?: string; resource: TranslationResource | TranslationMessages; @@ -34,7 +37,10 @@ export function createTranslationExtension(options: { }); } -/** @public */ +/** + * @public + * @deprecated Use {@link TranslationBlueprint} instead. + */ export namespace createTranslationExtension { export const translationDataRef = createExtensionDataRef< TranslationResource | TranslationMessages diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 267201db81..562cb728cb 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -25,4 +25,3 @@ export { createSignInPageExtension } from './createSignInPageExtension'; export { createThemeExtension } from './createThemeExtension'; export { createComponentExtension } from './createComponentExtension'; export { createTranslationExtension } from './createTranslationExtension'; -export { IconBundleBlueprint } from './IconBundleBlueprint'; diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index 263380b1f8..957ca50bba 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -22,6 +22,7 @@ export * from './analytics'; export * from './apis'; +export * from './blueprints'; export * from './components'; export * from './extensions'; export * from './icons'; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 924eba2cba..b9d1475074 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -127,7 +127,7 @@ describe('createExtensionBlueprint', () => { }, }); - const extension = TestExtensionBlueprint.make({ + const extension = TestExtensionBlueprint.makeWithOverrides({ name: 'my-extension', factory(origFactory) { return origFactory({ @@ -185,11 +185,8 @@ describe('createExtensionBlueprint', () => { }, }); - const extension = TestExtensionBlueprint.make({ + const extension = TestExtensionBlueprint.makeWithOverrides({ name: 'my-extension', - params: { - text: 'Hello, world!', - }, config: { schema: { something: z => z.string(), @@ -236,7 +233,7 @@ describe('createExtensionBlueprint', () => { }, }); - TestExtensionBlueprint.make({ + TestExtensionBlueprint.makeWithOverrides({ name: 'my-extension', params: { text: 'Hello, world!', @@ -266,11 +263,8 @@ describe('createExtensionBlueprint', () => { }, }); - const extension = TestExtensionBlueprint.make({ + const extension = TestExtensionBlueprint.makeWithOverrides({ name: 'my-extension', - params: { - text: 'Hello, world!', - }, config: { schema: { something: z => z.string(), @@ -341,65 +335,6 @@ describe('createExtensionBlueprint', () => { expect(true).toBe(true); }); - it('should allow providing callback for properties to set with params', () => { - type TestParams = { test: string }; - - const Blueprint = createExtensionBlueprint({ - kind: 'test-extension', - attachTo: { id: 'test', input: 'default' }, - name: (params: TestParams) => `${params.test}-name`, - output: [coreExtensionData.reactElement], - namespace: (props: TestParams) => props.test, - config: { - schema: (props: TestParams) => ({ - test: z => z.string().default(props.test), - }), - }, - factory(params: TestParams) { - return [coreExtensionData.reactElement(
{params.test}
)]; - }, - }); - - expect(Blueprint.make({ params: { test: 'hello' } })) - .toMatchInlineSnapshot(` - { - "$$type": "@backstage/ExtensionDefinition", - "attachTo": { - "id": "test", - "input": "default", - }, - "configSchema": { - "parse": [Function], - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "test": { - "default": "hello", - "type": "string", - }, - }, - "type": "object", - }, - }, - "disabled": false, - "factory": [Function], - "inputs": {}, - "kind": "test-extension", - "name": "hello-name", - "namespace": "hello", - "output": [ - [Function], - ], - "override": [Function], - "toString": [Function], - "version": "v2", - } - `); - - expect(true).toBe(true); - }); - it('should allow merging of inputs', () => { const blueprint = createExtensionBlueprint({ kind: 'test-extension', @@ -418,7 +353,7 @@ describe('createExtensionBlueprint', () => { }, }); - blueprint.make({ + blueprint.makeWithOverrides({ inputs: { test2: createExtensionInput([coreExtensionData.reactElement], { singleton: true, @@ -453,7 +388,7 @@ describe('createExtensionBlueprint', () => { }, }); - blueprint.make({ + blueprint.makeWithOverrides({ inputs: { // @ts-expect-error test: createExtensionInput([]), // Overrides are not allowed @@ -480,7 +415,7 @@ describe('createExtensionBlueprint', () => { }); const ext = toInternalExtensionDefinition( - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef2], factory(origFactory) { const parent = origFactory({}); @@ -509,7 +444,7 @@ describe('createExtensionBlueprint', () => { expect( factoryOutput( - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef1, testDataRef2], *factory(origFactory) { yield* origFactory({}); @@ -521,7 +456,7 @@ describe('createExtensionBlueprint', () => { expect( factoryOutput( - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef1, testDataRef2], factory(origFactory) { return [...origFactory({}), testDataRef2('bar')]; @@ -544,9 +479,9 @@ describe('createExtensionBlueprint', () => { }, }); - // @ts-expect-error - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef2.optional()], + // @ts-expect-error *factory() { yield testDataRef1('foo'); yield testDataRef2('bar'); @@ -555,7 +490,7 @@ describe('createExtensionBlueprint', () => { expect( factoryOutput( - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef2.optional()], *factory() { yield testDataRef2('bar'); @@ -578,9 +513,9 @@ describe('createExtensionBlueprint', () => { }, }); - // @ts-expect-error - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef1, testDataRef2], + // @ts-expect-error *factory(origFactory) { yield* origFactory({}); }, @@ -588,7 +523,7 @@ describe('createExtensionBlueprint', () => { expect( factoryOutput( - blueprint.make({ + blueprint.makeWithOverrides({ output: [testDataRef1, testDataRef2], *factory(origFactory) { yield* origFactory({}); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index f749f7d412..656e681a2b 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -52,14 +52,14 @@ export type CreateExtensionBlueprintOptions< TDataRefs extends { [name in string]: AnyExtensionDataRef }, > = { kind: TKind; - namespace?: TNamespace | ((params: TParams) => TNamespace); + namespace?: TNamespace; attachTo: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; output: Array; - name?: TName | ((params: TParams) => TName); + name?: TName; config?: { - schema: TConfigSchema | ((params: TParams) => TConfigSchema); + schema: TConfigSchema; }; factory( params: TParams, @@ -96,13 +96,32 @@ export interface ExtensionBlueprint< > { dataRefs: TDataRefs; + make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + >(args: { + namespace?: TNewNamespace; + name?: TNewName; + attachTo?: { id: string; input: string }; + disabled?: boolean; + params: TParams; + }): ExtensionDefinition< + TConfig, + TConfigInput, + UOutput, + TInputs, + TKind, + string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, + string | undefined extends TNewName ? TName : TNewName + >; + /** * Creates a new extension from the blueprint. * * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ - make< + makeWithOverrides< TNewNamespace extends string | undefined, TNewName extends string | undefined, TExtensionConfigSchema extends { @@ -116,52 +135,45 @@ export interface ExtensionBlueprint< { optional: boolean; singleton: boolean } >; }, - >( - args: { - namespace?: TNewNamespace; - name?: TNewName; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & - string}' is already defined in parent definition`; + >(args: { + namespace?: TNewNamespace; + name?: TNewName; + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof TInputs]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; }; - output?: Array; - config?: { - schema: TExtensionConfigSchema & { - [KName in keyof TConfig]?: `Error: Config key '${KName & - string}' is already defined in parent schema`; + }; + factory( + originalFactory: ( + params: TParams, + context?: { + config?: TConfig; + inputs?: Expand>; + }, + ) => ExtensionDataContainer, + context: { + node: AppNode; + config: TConfig & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; }; - }; - } & ( - | ({ - factory( - originalFactory: ( - params: TParams, - context?: { - config?: TConfig; - inputs?: Expand>; - }, - ) => ExtensionDataContainer, - context: { - node: AppNode; - config: TConfig & { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }; - inputs: Expand>; - }, - ): Iterable; - } & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - UFactoryOutput - >) - | { - params: TParams; - } - ), - ): ExtensionDefinition< + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + UFactoryOutput + >; + }): ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType @@ -243,7 +255,7 @@ class ExtensionBlueprintImpl< dataRefs: TDataRefs; - public make< + public makeWithOverrides< TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, @@ -264,11 +276,10 @@ class ExtensionBlueprintImpl< disabled?: boolean; inputs?: TExtraInputs; output?: Array; - params?: TParams; config?: { schema: TExtensionConfigSchema; }; - factory?( + factory( originalFactory: ( params: TParams, context?: { @@ -312,77 +323,82 @@ class ExtensionBlueprintImpl< > > > { - const optionsSchema = // can remove this args.params check with the split apart of .make - typeof this.options.config?.schema === 'function' && args.params - ? this.options.config?.schema(args.params) - : this.options.config?.schema; - const schema = { - ...optionsSchema, + ...this.options.config?.schema, ...args.config?.schema, } as TConfigSchema & TExtensionConfigSchema; - const namespace = - typeof this.options.namespace === 'function' && args.params - ? this.options.namespace(args.params) - : this.options.namespace; - - const name = - typeof this.options.name === 'function' && args.params - ? this.options.name(args.params) - : this.options.name; - return createExtension({ kind: this.options.kind, - namespace: args.namespace ?? namespace, - name: args.name ?? name, + namespace: args.namespace ?? this.options.namespace, + name: args.name ?? this.options.name, attachTo: args.attachTo ?? this.options.attachTo, disabled: args.disabled ?? this.options.disabled, inputs: { ...args.inputs, ...this.options.inputs }, output: args.output ?? this.options.output, config: Object.keys(schema).length === 0 ? undefined : { schema }, factory: ({ node, config, inputs }) => { - if (args.factory) { - return args.factory( - ( - innerParams: TParams, - innerContext?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - inputs?: Expand>; - }, - ): ExtensionDataContainer => { - return createDataContainer( - this.options.factory(innerParams, { - node, - config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden - }), - ); + return args.factory( + ( + innerParams: TParams, + innerContext?: { + config?: { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; + inputs?: Expand>; }, - { - node, - config, - inputs, - }, - ); - } else if (args.params) { - return this.options.factory(args.params, { + ): ExtensionDataContainer => { + return createDataContainer( + this.options.factory(innerParams, { + node, + config: innerContext?.config ?? config, + inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + }), + ); + }, + { node, config, - // TODO: Figure out types once legacy data map input type is gone - inputs: inputs as unknown as Expand< - ResolvedExtensionInputs - >, - }); - } - throw new Error('Either params or factory must be provided'); + inputs, + }, + ); }, } as CreateExtensionOptions); } + + public make< + TNewNamespace extends string | undefined = undefined, + TNewName extends string | undefined = undefined, + >(args: { + namespace?: TNewNamespace; + name?: TNewName; + attachTo?: { id: string; input: string }; + disabled?: boolean; + params: TParams; + }): ExtensionDefinition< + { + [key in keyof TConfigSchema]: z.infer>; + }, + z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + > + > { + return createExtension({ + kind: this.options.kind, + namespace: args.namespace ?? this.options.namespace, + name: args.name ?? this.options.name, + attachTo: args.attachTo ?? this.options.attachTo, + disabled: args.disabled ?? this.options.disabled, + inputs: this.options.inputs, + output: this.options.output, + config: this.options.config, + factory: ctx => this.options.factory(args.params, ctx), + } as CreateExtensionOptions); + } } /** diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index ce7da3701d..4bfc8392cb 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -19,7 +19,6 @@ import { MemoryRouter, Link } from 'react-router-dom'; import { RenderResult, render } from '@testing-library/react'; import { createSpecializedApp } from '@backstage/frontend-app-api'; import { - ExtensionDataValue, AppNode, AppTree, Extension, @@ -169,9 +168,7 @@ export class ExtensionTester { : [...internal.output, coreExtensionData.routePath], factory: params => { const parentOutput = Array.from( - internal.factory(params as any) as Iterable< - ExtensionDataValue - >, + internal.factory(params as any), ).filter(val => val.id !== coreExtensionData.routePath.id); return [...parentOutput, coreExtensionData.routePath('/')]; From 264e10f9cd1ae5cf0666f5e5f207d17e91a5b476 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 9 Aug 2024 15:43:33 +0200 Subject: [PATCH 135/393] chore: added changeset Signed-off-by: blam --- .changeset/tiny-dodos-prove-2.md | 5 +++++ .changeset/tiny-dodos-prove.md | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 .changeset/tiny-dodos-prove-2.md create mode 100644 .changeset/tiny-dodos-prove.md diff --git a/.changeset/tiny-dodos-prove-2.md b/.changeset/tiny-dodos-prove-2.md new file mode 100644 index 0000000000..91ac021c67 --- /dev/null +++ b/.changeset/tiny-dodos-prove-2.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. diff --git a/.changeset/tiny-dodos-prove.md b/.changeset/tiny-dodos-prove.md new file mode 100644 index 0000000000..6392e5050e --- /dev/null +++ b/.changeset/tiny-dodos-prove.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Refactor `.make` method on Blueprints into two different methods, `.make` and `.makeWithOverrides`. + +When using `createExtensionBlueprint` you can define parameters for the factory function, if you wish to take advantage of these parameters you should use `.make` when creating an extension instance of a Blueprint. If you wish to override more things other than the standard `attachTo`, `name`, `namespace` then you should use `.makeWithOverrides` instead. + +`.make` is reserved for simple creation of extension instances from Blueprints using higher level parameters, whereas `.makeWithOverrides` is lower level and you have more control over the final extension. From 0e615076857067573e03b8d13ea0e250083ae54e Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 9 Aug 2024 15:45:57 +0200 Subject: [PATCH 136/393] chore: updating api-reports and documentation for exported blueprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 18 +++++++++--------- .../src/blueprints/ApiBlueprint.ts | 7 ++++++- .../src/blueprints/AppRootElementBlueprint.ts | 7 ++++++- .../src/blueprints/AppRootWrapperBlueprint.tsx | 8 +++++++- .../src/blueprints/NavItemBlueprint.ts | 6 +++++- .../src/blueprints/NavLogoBlueprint.ts | 6 +++++- .../src/blueprints/PageBlueprint.tsx | 6 +++++- .../src/blueprints/SignInPageBlueprint.tsx | 6 +++++- .../src/blueprints/ThemeBlueprint.ts | 6 +++++- .../src/blueprints/TranslationBlueprint.ts | 6 +++++- 10 files changed, 58 insertions(+), 18 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 2d3f1d3278..f39add924a 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -189,7 +189,7 @@ export type AnyRoutes = { [name in string]: RouteRef | SubRouteRef; }; -// @public (undocumented) +// @public export const ApiBlueprint: ExtensionBlueprint< 'api', undefined, @@ -261,7 +261,7 @@ export interface AppNodeSpec { readonly source?: BackstagePlugin; } -// @public (undocumented) +// @public export const AppRootElementBlueprint: ExtensionBlueprint< 'app-root-element', undefined, @@ -276,7 +276,7 @@ export const AppRootElementBlueprint: ExtensionBlueprint< never >; -// @public (undocumented) +// @public export const AppRootWrapperBlueprint: ExtensionBlueprint< 'app-root-wrapper', undefined, @@ -1696,7 +1696,7 @@ export interface LegacyExtensionInput< export { microsoftAuthApiRef }; -// @public (undocumented) +// @public export const NavItemBlueprint: ExtensionBlueprint< 'nav-item', undefined, @@ -1731,7 +1731,7 @@ export const NavItemBlueprint: ExtensionBlueprint< } >; -// @public (undocumented) +// @public export const NavLogoBlueprint: ExtensionBlueprint< 'nav-logo', undefined, @@ -1781,7 +1781,7 @@ export { oneloginAuthApiRef }; export { OpenIdConnectApi }; -// @public (undocumented) +// @public export const PageBlueprint: ExtensionBlueprint< 'page', undefined, @@ -1938,7 +1938,7 @@ export { SessionApi }; export { SessionState }; -// @public (undocumented) +// @public export const SignInPageBlueprint: ExtensionBlueprint< 'sign-in-page', undefined, @@ -1981,7 +1981,7 @@ export interface SubRouteRef< readonly T: TParams; } -// @public (undocumented) +// @public export const ThemeBlueprint: ExtensionBlueprint< 'theme', 'app', @@ -1998,7 +1998,7 @@ export const ThemeBlueprint: ExtensionBlueprint< } >; -// @public (undocumented) +// @public export const TranslationBlueprint: ExtensionBlueprint< 'translation', undefined, diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts index da12faef1a..b5ba9e05f6 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createExtensionBlueprint } from '../wiring'; import { createApiExtension } from '../extensions/createApiExtension'; import { AnyApiFactory } from '@backstage/core-plugin-api'; -/** @public */ +/** + * Creates utility API extensions. + * + * @public + */ export const ApiBlueprint = createExtensionBlueprint({ kind: 'api', attachTo: { id: 'app', input: 'apis' }, diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts index e44942979d..bc6304923a 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts @@ -15,7 +15,12 @@ */ import { coreExtensionData, createExtensionBlueprint } from '../wiring'; -/** @public */ +/** + * Creates extensions that render a React element at the app root, outside of + * the app layout. This is useful for example for shared popups and similar. + * + * @public + */ export const AppRootElementBlueprint = createExtensionBlueprint({ kind: 'app-root-element', attachTo: { id: 'app/root', input: 'elements' }, diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index db63550880..c54686f54f 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -19,7 +19,13 @@ import { ComponentType, PropsWithChildren } from 'react'; import { createExtensionBlueprint } from '../wiring'; import { createAppRootWrapperExtension } from '../extensions/createAppRootWrapperExtension'; -/** @public */ +/** + * Creates a extensions that render a React wrapper at the app root, enclosing + * the app layout. This is useful for example for adding global React contexts + * and similar. + * + * @public + */ export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', attachTo: { id: 'app/root', input: 'wrappers' }, diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 5391d0dec2..20f57421c7 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -19,7 +19,11 @@ import { RouteRef } from '../routing'; import { createExtensionBlueprint } from '../wiring'; import { createNavItemExtension } from '../extensions/createNavItemExtension'; -/** @public */ +/** + * Creates extensions that make up the items of the nav bar. + * + * @public + */ export const NavItemBlueprint = createExtensionBlueprint({ kind: 'nav-item', attachTo: { id: 'app/nav', input: 'items' }, diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts index f31b3cec9b..8066646ca3 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts @@ -17,7 +17,11 @@ import { createExtensionBlueprint } from '../wiring'; import { createNavLogoExtension } from '../extensions/createNavLogoExtension'; -/** @public */ +/** + * Creates an extension that replaces the logo in the nav bar with your own. + * + * @public + */ export const NavLogoBlueprint = createExtensionBlueprint({ kind: 'nav-logo', attachTo: { id: 'app/nav', input: 'logos' }, diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index bfcf4a7934..b224ea491e 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -18,7 +18,11 @@ import { RouteRef } from '../routing'; import { coreExtensionData, createExtensionBlueprint } from '../wiring'; import { ExtensionBoundary } from '../components'; -/** @public */ +/** + * Createx extensions that are routable React page components. + * + * @public + */ export const PageBlueprint = createExtensionBlueprint({ kind: 'page', attachTo: { id: 'app/routes', input: 'routes' }, diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index 2727335d8a..f87ae91452 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -19,7 +19,11 @@ import { createSignInPageExtension } from '../extensions/createSignInPageExtensi import { SignInPageProps } from '@backstage/core-plugin-api'; import { ExtensionBoundary } from '../components'; -/** @public */ +/** + * Creates an extension that replaces the sign in page. + * + * @public + */ export const SignInPageBlueprint = createExtensionBlueprint({ kind: 'sign-in-page', attachTo: { id: 'app/root', input: 'signInPage' }, diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index cd97e0437f..686c498c06 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -18,7 +18,11 @@ import { AppTheme } from '@backstage/core-plugin-api'; import { createExtensionBlueprint } from '../wiring'; import { createThemeExtension } from '../extensions/createThemeExtension'; -/** @public */ +/** + * Creates an extension that adds/replaces an app theme. + * + * @public + */ export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', namespace: 'app', diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts index c70ca56f6b..bdb36e2191 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts @@ -18,7 +18,11 @@ import { createExtensionBlueprint } from '../wiring'; import { createTranslationExtension } from '../extensions/createTranslationExtension'; import { TranslationMessages, TranslationResource } from '../translation'; -/** @public */ +/** + * Creates an extension that adds translations to your app. + * + * @public + */ export const TranslationBlueprint = createExtensionBlueprint({ kind: 'translation', attachTo: { id: 'app', input: 'translations' }, From 3a35172d2ebdc977b6d5f91834f0072001761b00 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Fri, 9 Aug 2024 14:18:22 +0300 Subject: [PATCH 137/393] fix: EventEmitter memory leak in development utilities Signed-off-by: Heikki Hellgren --- .changeset/new-knives-fly.md | 5 +++ packages/backend-dev-utils/src/ipcClient.ts | 43 ++++++++++++++------- 2 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 .changeset/new-knives-fly.md diff --git a/.changeset/new-knives-fly.md b/.changeset/new-knives-fly.md new file mode 100644 index 0000000000..f9ac945cad --- /dev/null +++ b/.changeset/new-knives-fly.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dev-utils': patch +--- + +Fix `EventEmitter` memory leak in the development utilities diff --git a/packages/backend-dev-utils/src/ipcClient.ts b/packages/backend-dev-utils/src/ipcClient.ts index d9ec7821de..6e1d3d181e 100644 --- a/packages/backend-dev-utils/src/ipcClient.ts +++ b/packages/backend-dev-utils/src/ipcClient.ts @@ -35,6 +35,8 @@ type Response = error: Error; }; +type ResponseHandler = (response: Response) => void; + const requestType = '@backstage/cli/channel/request'; const responseType = '@backstage/cli/channel/response'; @@ -48,21 +50,40 @@ const IPC_TIMEOUT_MS = 5000; export class BackstageIpcClient { #messageId = 0; #sendMessage: SendMessage; + #handlers: Map = new Map(); /** * Creates a new client if we're in a child process with IPC and BACKSTAGE_CLI_CHANNEL is set. */ static create(): BackstageIpcClient | undefined { const sendMessage = process.send?.bind(process); - return sendMessage && process.env.BACKSTAGE_CLI_CHANNEL - ? new BackstageIpcClient(sendMessage) - : undefined; + const client = + sendMessage && process.env.BACKSTAGE_CLI_CHANNEL + ? new BackstageIpcClient(sendMessage) + : undefined; + + if (client) { + process.on('message', (message, _sendHandle) => + client.handleMessage(message, _sendHandle), + ); + } + + return client; } constructor(sendMessage: SendMessage) { this.#sendMessage = sendMessage; } + private handleMessage(message: unknown, _sendHandle: unknown) { + const isResponse = (msg: unknown): msg is Response => + (msg as Response)?.type === responseType; + + if (isResponse(message)) { + this.#handlers.get(message.id)?.(message); + } + } + /** * Send a request to the parent process and wait for a response. */ @@ -82,17 +103,10 @@ export class BackstageIpcClient { let timeout: NodeJS.Timeout | undefined = undefined; - const messageHandler = (response: Response) => { - if (response?.type !== responseType) { - return; - } - if (response.id !== id) { - return; - } - + const responseHandler: ResponseHandler = (response: Response) => { clearTimeout(timeout); timeout = undefined; - process.removeListener('message', messageHandler); + this.#handlers.delete(id); if ('error' in response) { const error = new Error(response.error.message); @@ -107,12 +121,11 @@ export class BackstageIpcClient { timeout = setTimeout(() => { reject(new Error(`IPC request '${method}' with ID ${id} timed out`)); - process.removeListener('message', messageHandler); + this.#handlers.delete(id); }, IPC_TIMEOUT_MS); timeout.unref(); - process.addListener('message', messageHandler as () => void); - + this.#handlers.set(id, responseHandler); this.#sendMessage(request, (e: Error) => { if (e) { reject(e); From 6d898d847533eb8eb5c065f20f409d8c501e386d Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 9 Aug 2024 12:07:10 -0600 Subject: [PATCH 138/393] Switch process polyfill to require.resolve Signed-off-by: Tim Hansen --- .changeset/funny-bears-sort.md | 5 +++++ packages/cli/src/lib/bundler/config.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/funny-bears-sort.md diff --git a/.changeset/funny-bears-sort.md b/.changeset/funny-bears-sort.md new file mode 100644 index 0000000000..a3dad0f489 --- /dev/null +++ b/.changeset/funny-bears-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Switched the `process` polyfill to use `require.resolve` for greater compatability. diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 42cdae24e3..da7b378fbd 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -180,7 +180,7 @@ export async function createConfig( // to remove this eventually! plugins.push( new ProvidePlugin({ - process: 'process/browser', + process: require.resolve('process/browser'), Buffer: ['buffer', 'Buffer'], }), ); From 27794d1089ba154b3bd7625ca55429d75d20a58b Mon Sep 17 00:00:00 2001 From: Sydney Achinger <78113809+squid-ney@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:33:47 -0400 Subject: [PATCH 139/393] Add ability to override theme options for the TechDocsReaderPage. (#25956) * Add ability to override theme options for the TechDocsReaderPage. --------- Signed-off-by: Sydney Achinger --- .changeset/healthy-fireants-jump.md | 5 ++ plugins/techdocs/api-report.md | 2 + .../TechDocsReaderPage.test.tsx | 26 ++++++++ .../TechDocsReaderPage/TechDocsReaderPage.tsx | 59 +++++++++++-------- 4 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 .changeset/healthy-fireants-jump.md diff --git a/.changeset/healthy-fireants-jump.md b/.changeset/healthy-fireants-jump.md new file mode 100644 index 0000000000..7a11015b80 --- /dev/null +++ b/.changeset/healthy-fireants-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 90834c6fcb..781d8db422 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -30,6 +30,7 @@ import { TechDocsApi as TechDocsApi_2 } from '@backstage/plugin-techdocs-react'; import { TechDocsEntityMetadata as TechDocsEntityMetadata_2 } from '@backstage/plugin-techdocs-react'; import { TechDocsMetadata as TechDocsMetadata_2 } from '@backstage/plugin-techdocs-react'; import { TechDocsStorageApi as TechDocsStorageApi_2 } from '@backstage/plugin-techdocs-react'; +import { ThemeOptions } from '@material-ui/core/styles'; import { ToolbarProps } from '@material-ui/core/Toolbar'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; @@ -389,6 +390,7 @@ export type TechDocsReaderPageHeaderProps = PropsWithChildren<{ export type TechDocsReaderPageProps = { entityRef?: CompoundEntityRef; children?: TechDocsReaderPageRenderFunction | ReactNode; + overrideThemeOptions?: Partial; }; // @public diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx index ed8b05c661..5e8cd7af27 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx @@ -252,4 +252,30 @@ describe('', () => { expect(rendered.getByText('the page')).toBeInTheDocument(); }); + + it('should apply overrideThemeOptions', async () => { + const overrideThemeOptions = { + typography: { fontFamily: 'Comic Sans MS' }, + }; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes, + }, + ); + + const text = rendered.getAllByText(mockTechDocsMetadata.site_name)[0]; + + expect(text).toHaveStyle('fontFamily: Comic Sans MS'); + }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index b1b9ecb13b..0f48df7f39 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -37,6 +37,8 @@ import { } from '@backstage/core-plugin-api'; import { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react'; +import { ThemeOptions } from '@material-ui/core/styles'; +import { createTheme, ThemeProvider, useTheme } from '@material-ui/core/styles'; /* An explanation for the multiple ways of customizing the TechDocs reader page @@ -151,6 +153,7 @@ export const TechDocsReaderLayout = (props: TechDocsReaderLayoutProps) => { export type TechDocsReaderPageProps = { entityRef?: CompoundEntityRef; children?: TechDocsReaderPageRenderFunction | ReactNode; + overrideThemeOptions?: Partial; }; /** @@ -159,6 +162,12 @@ export type TechDocsReaderPageProps = { * @public */ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { + const currentTheme = useTheme(); + + const readerPageTheme = createTheme({ + ...currentTheme, + ...(props.overrideThemeOptions || {}), + }); const { kind, name, namespace } = useRouteRefParams(rootDocsRouteRef); const { children, entityRef = { kind, name, namespace } } = props; @@ -179,33 +188,37 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { // As explained above, "page" is configuration 4 and is 1 return ( - - - {(page as JSX.Element) || } - - + + + + {(page as JSX.Element) || } + + + ); } // As explained above, a render function is configuration 3 and React element is 2 return ( - - - {({ metadata, entityMetadata, onReady }) => ( -
- - {children instanceof Function - ? children({ - entityRef, - techdocsMetadataValue: metadata.value, - entityMetadataValue: entityMetadata.value, - onReady, - }) - : children} - -
- )} -
-
+ + + + {({ metadata, entityMetadata, onReady }) => ( +
+ + {children instanceof Function + ? children({ + entityRef, + techdocsMetadataValue: metadata.value, + entityMetadataValue: entityMetadata.value, + onReady, + }) + : children} + +
+ )} +
+
+
); }; From c33f8df6ebc6516706cb6c7a04b2b33c67836118 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 9 Aug 2024 16:34:52 +0200 Subject: [PATCH 140/393] frontend-plugin-api: initial support for blueprint input overrides Signed-off-by: Patrik Oldsberg --- .../wiring/createExtensionBlueprint.test.tsx | 246 +++++++++++++++++- .../src/wiring/createExtensionBlueprint.ts | 134 +++++++++- 2 files changed, 372 insertions(+), 8 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index b9d1475074..003090668b 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -16,9 +16,15 @@ import React from 'react'; import { coreExtensionData } from './coreExtensionData'; -import { createExtensionBlueprint } from './createExtensionBlueprint'; +import { + createDataContainer, + createExtensionBlueprint, +} from './createExtensionBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { createExtensionDataRef } from './createExtensionDataRef'; +import { + ExtensionDataValue, + createExtensionDataRef, +} from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; import { @@ -28,12 +34,15 @@ import { function unused(..._any: any[]) {} -function factoryOutput(ext: ExtensionDefinition) { +function factoryOutput( + ext: ExtensionDefinition, + inputs: unknown = undefined, +) { const int = toInternalExtensionDefinition(ext); if (int.version !== 'v2') { throw new Error('Expected v2 extension'); } - return Array.from(int.factory({} as any)); + return Array.from(int.factory({ inputs } as any)); } describe('createExtensionBlueprint', () => { @@ -335,6 +344,235 @@ describe('createExtensionBlueprint', () => { expect(true).toBe(true); }); + it('should be able to override inputs when calling original factory', () => { + const outputRef = createExtensionDataRef().with({ id: 'output' }); + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const Blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + inputs: { + opt: createExtensionInput([testDataRef1.optional()], { + singleton: true, + optional: true, + }), + single: createExtensionInput([testDataRef1, testDataRef2.optional()], { + singleton: true, + }), + multi: createExtensionInput([testDataRef1]), + }, + output: [outputRef], + factory(_, { inputs }) { + return [ + outputRef({ + opt: inputs.opt?.get(testDataRef1) ?? 'none', + single: inputs.single.get(testDataRef1), + singleOpt: inputs.single.get(testDataRef2) ?? 'none', + multi: inputs.multi.map(i => i.get(testDataRef1)).join(','), + }), + ]; + }, + }); + + const mockInput = (node: string, ...data: ExtensionDataValue[]) => + Object.assign(createDataContainer(data), { + node, + }); + const mockParentInputs = { + opt: mockInput('node-opt', testDataRef1('orig-opt')), + single: mockInput('node-single', testDataRef1('orig-single')), + multi: [ + mockInput('node-multi1', testDataRef1('orig-multi1')), + mockInput('node-multi2', testDataRef1('orig-multi2')), + ], + }; + + // All values provided + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + opt: [testDataRef1('opt')], + single: [testDataRef1('single'), testDataRef2('singleOpt')], + multi: [[testDataRef1('multi1')], [testDataRef1('multi2')]], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'opt', + single: 'single', + singleOpt: 'singleOpt', + multi: 'multi1,multi2', + }), + ]); + + // Minimal values provided + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + single: [testDataRef1('single')], + multi: [[testDataRef1('multi1')], [testDataRef1('multi2')]], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'none', + single: 'single', + singleOpt: 'none', + multi: 'multi1,multi2', + }), + ]); + + // Not enough values provided, checked at runtime + expect(() => + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + single: [], + multi: [], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, + ); + + // Wrong value provided + expect(() => + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + // @ts-expect-error + opt: [testDataRef2('opt')], + // @ts-expect-error + single: [testDataRef1('single'), outputRef({})], + multi: [ + // @ts-expect-error + [testDataRef2('multi1')], + ], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, + ); + + // Forwarding entire inputs object + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory({}, { inputs }); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }), + ]); + + // Forwarding individual outputs + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: inputs.multi, + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }), + ]); + + // Overriding based on original input + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + single: [ + testDataRef1(`override-${inputs.single.get(testDataRef1)}`), + testDataRef2('new-singleOpt'), + ], + multi: inputs.multi.map(i => [ + testDataRef1(`override-${i.get(testDataRef1)}`), + ]), + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'none', + single: 'override-orig-single', + singleOpt: 'new-singleOpt', + multi: 'override-orig-multi1,override-orig-multi2', + }), + ]); + }); + it('should allow merging of inputs', () => { const blueprint = createExtensionBlueprint({ kind: 'test-extension', diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 656e681a2b..2bc5fda43d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -20,6 +20,7 @@ import { CreateExtensionOptions, ExtensionDataContainer, ExtensionDefinition, + ResolvedExtensionInput, ResolvedExtensionInputs, VerifyExtensionFactoryOutput, createExtension, @@ -29,6 +30,7 @@ import { ExtensionInput } from './createExtensionInput'; import { AnyExtensionDataRef, ExtensionDataRef, + ExtensionDataRefToValue, ExtensionDataValue, } from './createExtensionDataRef'; @@ -75,6 +77,61 @@ export type CreateExtensionBlueprintOptions< dataRefs?: TDataRefs; } & VerifyExtensionFactoryOutput; +/** @ignore */ +type ResolveInputValueOverrides< + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + } = { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, +> = Expand< + { + [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< + any, + { + optional: infer IOptional extends boolean; + singleton: boolean; + } + > + ? IOptional extends true + ? never + : KName + : never]: TInputs[KName] extends ExtensionInput< + infer IDataRefs, + { optional: boolean; singleton: infer ISingleton extends boolean } + > + ? ISingleton extends true + ? Iterable> + : Array>> + : never; + } & { + [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< + any, + { + optional: infer IOptional extends boolean; + singleton: boolean; + } + > + ? IOptional extends true + ? KName + : never + : never]?: TInputs[KName] extends ExtensionInput< + infer IDataRefs, + { optional: boolean; singleton: infer ISingleton extends boolean } + > + ? ISingleton extends true + ? Iterable> + : Array>> + : never; + } +>; + /** * @public */ @@ -156,7 +213,7 @@ export interface ExtensionBlueprint< params: TParams, context?: { config?: TConfig; - inputs?: Expand>; + inputs?: ResolveInputValueOverrides; }, ) => ExtensionDataContainer, context: { @@ -288,7 +345,7 @@ class ExtensionBlueprintImpl< ReturnType >; }; - inputs?: Expand>; + inputs?: ResolveInputValueOverrides; }, ) => ExtensionDataContainer, context: { @@ -347,14 +404,83 @@ class ExtensionBlueprintImpl< ReturnType >; }; - inputs?: Expand>; + inputs?: ResolveInputValueOverrides; }, ): ExtensionDataContainer => { + const overrideInputs = innerContext?.inputs; + const declaredInputs = this.options.inputs; + + let forwardedInputs = inputs; + if (overrideInputs && declaredInputs) { + const newInputs = {} as { + [KName in keyof TInputs]?: + | ({ node: AppNode } & ExtensionDataContainer) + | Array<{ node: AppNode } & ExtensionDataContainer>; + }; + forwardedInputs = newInputs as typeof inputs; + + for (const name in declaredInputs) { + if (!Object.hasOwn(declaredInputs, name)) { + continue; + } + const declaredInput = declaredInputs[name]; + const providedData = overrideInputs[name]; + if (declaredInput.config.singleton) { + // We know the passed in input will match the declared inputs, so this case is safe + const originalInput = ( + inputs as Record< + string, + { node: AppNode } & ExtensionDataContainer + > + )[name]; + if (providedData) { + const providedContainer = createDataContainer( + providedData as Iterable>, + ); + if (!originalInput) { + throw new Error( + `A data override was provided for input '${name}', but no original input was present.`, + ); + } + newInputs[name] = Object.assign(providedContainer, { + name: (originalInput as ResolvedExtensionInput).node, + }) as any; + } + } else { + // We know the passed in input will match the declared inputs, so this case is safe + const originalInput = ( + inputs as Record< + string, + Array<{ node: AppNode } & ExtensionDataContainer> + > + )[name]; + if (!Array.isArray(providedData)) { + throw new Error( + `Invalid override provided for input '${name}', expected an array`, + ); + } + if (originalInput.length !== providedData.length) { + throw new Error( + `Invalid override provided for input '${name}', when overriding the input data the length must match the original input data`, + ); + } + newInputs[name] = providedData.map((data, i) => { + const providedContainer = createDataContainer( + data as Iterable>, + ); + return Object.assign(providedContainer, { + name: (originalInput[i] as ResolvedExtensionInput) + .node, + }) as any; + }); + } + } + } return createDataContainer( this.options.factory(innerParams, { node, config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + inputs: forwardedInputs as any, // TODO: Might be able to improve this once legacy inputs are gone }), ); }, From eab84be9336594fa19ef1698b7a7f2ed906bce09 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Aug 2024 13:56:03 +0200 Subject: [PATCH 141/393] frontend-plugin-api: validate input override values Signed-off-by: Patrik Oldsberg --- .../wiring/createExtensionBlueprint.test.tsx | 32 ++++++++++++++++--- .../src/wiring/createExtensionBlueprint.ts | 23 +++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 003090668b..fa85cbf981 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -443,17 +443,17 @@ describe('createExtensionBlueprint', () => { }), ]); - // Not enough values provided, checked at runtime + // Mismatched input override length expect(() => factoryOutput( Blueprint.makeWithOverrides({ - factory(origFactory) { + factory(origFactory, { inputs }) { return origFactory( {}, { inputs: { - single: [], - multi: [], + ...inputs, + multi: [[testDataRef1('multi1')]], }, }, ); @@ -465,6 +465,28 @@ describe('createExtensionBlueprint', () => { `"Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, ); + // Required input not provided + expect(() => + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + ...inputs, + single: [testDataRef2('singleOpt')], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required data values for 'test1'"`, + ); + // Wrong value provided expect(() => factoryOutput( @@ -490,7 +512,7 @@ describe('createExtensionBlueprint', () => { mockParentInputs, ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, + `"Invalid data value provided, 'test2' was not declared"`, ); // Forwarding entire inputs object diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 2bc5fda43d..2ad58e2ef9 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -259,13 +259,34 @@ export function createDataContainer( ? ExtensionDataValue : never >, + declaredRefs?: ExtensionDataRef[], ): ExtensionDataContainer { const container = new Map>(); + const verifyRefs = + declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref])); for (const output of values) { + if (verifyRefs) { + if (!verifyRefs.delete(output.id)) { + throw new Error( + `Invalid data value provided, '${output.id}' was not declared`, + ); + } + } container.set(output.id, output); } + const remainingRefs = + verifyRefs && + Array.from(verifyRefs.values()).filter(ref => !ref.config.optional); + if (remainingRefs && remainingRefs.length > 0) { + throw new Error( + `Missing required data values for '${remainingRefs + .map(ref => ref.id) + .join(', ')}'`, + ); + } + return { get(ref) { return container.get(ref.id)?.value; @@ -436,6 +457,7 @@ class ExtensionBlueprintImpl< if (providedData) { const providedContainer = createDataContainer( providedData as Iterable>, + declaredInput.extensionData, ); if (!originalInput) { throw new Error( @@ -467,6 +489,7 @@ class ExtensionBlueprintImpl< newInputs[name] = providedData.map((data, i) => { const providedContainer = createDataContainer( data as Iterable>, + declaredInput.extensionData, ); return Object.assign(providedContainer, { name: (originalInput[i] as ResolvedExtensionInput) From 51b271f4d387cd5e1f72d7d70e110270a91b8ed1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Aug 2024 17:48:36 +0200 Subject: [PATCH 142/393] frontend-plugin-api: validate blueprint original factory output Signed-off-by: Patrik Oldsberg --- .../wiring/createExtensionBlueprint.test.tsx | 37 +++++++++++++++++++ .../src/wiring/createExtensionBlueprint.ts | 1 + 2 files changed, 38 insertions(+) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index fa85cbf981..efca17459b 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -689,6 +689,43 @@ describe('createExtensionBlueprint', () => { expect(factoryOutput(ext)).toEqual([testDataRef2('foobar')]); }); + it('should reject invalid output from original factory', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + expect(() => + factoryOutput( + // @ts-expect-error + createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1], + factory() { + return [testDataRef2('foo')]; + }, + }).makeWithOverrides({ factory: orig => orig({}) }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid data value provided, 'test2' was not declared"`, + ); + + expect(() => + factoryOutput( + // @ts-expect-error + createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1], + factory() { + return []; + }, + }).makeWithOverrides({ factory: orig => orig({}) }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required data values for 'test1'"`, + ); + }); + it('should allow returning of the parent data container', () => { const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 2ad58e2ef9..8ce3dab4d5 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -505,6 +505,7 @@ class ExtensionBlueprintImpl< config: innerContext?.config ?? config, inputs: forwardedInputs as any, // TODO: Might be able to improve this once legacy inputs are gone }), + this.options.output, ); }, { From 9f476d1687e805cd3a5edec426c25bf51c5b9217 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 11 Aug 2024 12:54:01 +0200 Subject: [PATCH 143/393] frontend-plugin-api: refactor out blueprint input overrides Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtensionBlueprint.ts | 152 +++++++++--------- 1 file changed, 80 insertions(+), 72 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 8ce3dab4d5..88f3a6e9dd 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -297,6 +297,81 @@ export function createDataContainer( } as ExtensionDataContainer; } +function expectArray(value: T | T[]): T[] { + return value as T[]; +} +function expectItem(value: T | T[]): T { + return value as T; +} + +/** @internal */ +export function resolveInputOverrides( + declaredInputs?: { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + inputs?: { + [KName in string]?: + | ({ node: AppNode } & ExtensionDataContainer) + | Array<{ node: AppNode } & ExtensionDataContainer>; + }, + inputOverrides?: ResolveInputValueOverrides, +) { + if (!declaredInputs || !inputs || !inputOverrides) { + return inputs; + } + + const newInputs: typeof inputs = {}; + for (const name in declaredInputs) { + if (!Object.hasOwn(declaredInputs, name)) { + continue; + } + const declaredInput = declaredInputs[name]; + const providedData = inputOverrides[name]; + if (declaredInput.config.singleton) { + const originalInput = expectItem(inputs[name]); + if (providedData) { + const providedContainer = createDataContainer( + providedData as Iterable>, + declaredInput.extensionData, + ); + if (!originalInput) { + throw new Error( + `A data override was provided for input '${name}', but no original input was present.`, + ); + } + newInputs[name] = Object.assign(providedContainer, { + name: (originalInput as ResolvedExtensionInput).node, + }) as any; + } + } else { + const originalInput = expectArray(inputs[name]); + if (!Array.isArray(providedData)) { + throw new Error( + `Invalid override provided for input '${name}', expected an array`, + ); + } + if (originalInput.length !== providedData.length) { + throw new Error( + `Invalid override provided for input '${name}', when overriding the input data the length must match the original input data`, + ); + } + newInputs[name] = providedData.map((data, i) => { + const providedContainer = createDataContainer( + data as Iterable>, + declaredInput.extensionData, + ); + return Object.assign(providedContainer, { + name: (originalInput[i] as ResolvedExtensionInput).node, + }) as any; + }); + } + } + return newInputs; +} + /** * @internal */ @@ -428,82 +503,15 @@ class ExtensionBlueprintImpl< inputs?: ResolveInputValueOverrides; }, ): ExtensionDataContainer => { - const overrideInputs = innerContext?.inputs; - const declaredInputs = this.options.inputs; - - let forwardedInputs = inputs; - if (overrideInputs && declaredInputs) { - const newInputs = {} as { - [KName in keyof TInputs]?: - | ({ node: AppNode } & ExtensionDataContainer) - | Array<{ node: AppNode } & ExtensionDataContainer>; - }; - forwardedInputs = newInputs as typeof inputs; - - for (const name in declaredInputs) { - if (!Object.hasOwn(declaredInputs, name)) { - continue; - } - const declaredInput = declaredInputs[name]; - const providedData = overrideInputs[name]; - if (declaredInput.config.singleton) { - // We know the passed in input will match the declared inputs, so this case is safe - const originalInput = ( - inputs as Record< - string, - { node: AppNode } & ExtensionDataContainer - > - )[name]; - if (providedData) { - const providedContainer = createDataContainer( - providedData as Iterable>, - declaredInput.extensionData, - ); - if (!originalInput) { - throw new Error( - `A data override was provided for input '${name}', but no original input was present.`, - ); - } - newInputs[name] = Object.assign(providedContainer, { - name: (originalInput as ResolvedExtensionInput).node, - }) as any; - } - } else { - // We know the passed in input will match the declared inputs, so this case is safe - const originalInput = ( - inputs as Record< - string, - Array<{ node: AppNode } & ExtensionDataContainer> - > - )[name]; - if (!Array.isArray(providedData)) { - throw new Error( - `Invalid override provided for input '${name}', expected an array`, - ); - } - if (originalInput.length !== providedData.length) { - throw new Error( - `Invalid override provided for input '${name}', when overriding the input data the length must match the original input data`, - ); - } - newInputs[name] = providedData.map((data, i) => { - const providedContainer = createDataContainer( - data as Iterable>, - declaredInput.extensionData, - ); - return Object.assign(providedContainer, { - name: (originalInput[i] as ResolvedExtensionInput) - .node, - }) as any; - }); - } - } - } return createDataContainer( this.options.factory(innerParams, { node, config: innerContext?.config ?? config, - inputs: forwardedInputs as any, // TODO: Might be able to improve this once legacy inputs are gone + inputs: resolveInputOverrides( + this.options.inputs, + inputs, + innerContext?.inputs, + ) as any, // TODO: Might be able to improve this once legacy inputs are gone }), this.options.output, ); From 66c6bbcef5d0fe0b904f5841141ce50a65d1961b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 11 Aug 2024 19:57:06 +0200 Subject: [PATCH 144/393] frontend-app-api: fix for input extension data containers not being iterable at runtime Signed-off-by: Patrik Oldsberg --- .../src/tree/instantiateAppNodeTree.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 65626e0be1..c70e351105 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -92,6 +92,16 @@ function resolveInputDataContainer( get(ref) { return dataMap.get(ref.id); }, + *[Symbol.iterator]() { + for (const [id, value] of dataMap) { + // TODO: Would be better to be able to create a new instance using the ref here instead + yield { + $$type: '@backstage/ExtensionDataValue', + id, + value, + }; + } + }, } as { node: AppNode } & ExtensionDataContainer; } From cea81165f45f3cb9c6488ce66f18cda7d07c5c91 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 11 Aug 2024 19:57:42 +0200 Subject: [PATCH 145/393] frontend-plugin-api: add support for overriding input values when overriding extensions Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 292 ++++++++++++++++++ .../src/wiring/createExtension.ts | 19 +- .../src/wiring/createExtensionBlueprint.ts | 2 +- 3 files changed, 307 insertions(+), 6 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index f9e39e98de..9d2aff5d99 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -732,5 +732,297 @@ describe('createExtension', () => { }).data(stringDataRef), ).toBe('foo-hello-override-world'); }); + + it('should be able to override input values', () => { + const outputRef = createExtensionDataRef().with({ + id: 'output', + }); + const testDataRef1 = createExtensionDataRef().with({ + id: 'test1', + }); + const testDataRef2 = createExtensionDataRef().with({ + id: 'test2', + }); + + const subject = createExtension({ + name: 'subject', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + opt: createExtensionInput([testDataRef1.optional()], { + singleton: true, + optional: true, + }), + single: createExtensionInput( + [testDataRef1, testDataRef2.optional()], + { + singleton: true, + }, + ), + multi: createExtensionInput([testDataRef1]), + }, + output: [outputRef], + factory({ inputs }) { + return [ + outputRef({ + opt: inputs.opt?.get(testDataRef1) ?? 'none', + single: inputs.single.get(testDataRef1), + singleOpt: inputs.single.get(testDataRef2) ?? 'none', + multi: inputs.multi.map(i => i.get(testDataRef1)).join(','), + }), + ]; + }, + }); + + const optExt = createExtension({ + name: 'opt', + attachTo: { id: 'subject', input: 'opt' }, + output: [testDataRef1], + factory: () => [testDataRef1('orig-opt')], + }); + + const singleExt = createExtension({ + name: 'single', + attachTo: { id: 'subject', input: 'single' }, + output: [testDataRef1, testDataRef2.optional()], + factory: () => [testDataRef1('orig-single')], + }); + + const multi1Ext = createExtension({ + name: 'multi1', + attachTo: { id: 'subject', input: 'multi' }, + output: [testDataRef1], + factory: () => [testDataRef1('orig-multi1')], + }); + + const multi2Ext = createExtension({ + name: 'multi2', + attachTo: { id: 'subject', input: 'multi' }, + output: [testDataRef1], + factory: () => [testDataRef1('orig-multi2')], + }); + + expect( + createExtensionTester(subject) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }); + + // All values provided + expect( + createExtensionTester( + subject.override({ + factory(originalFactory) { + return originalFactory({ + inputs: { + opt: [testDataRef1('opt')], + single: [testDataRef1('single'), testDataRef2('singleOpt')], + multi: [[testDataRef1('multi1')], [testDataRef1('multi2')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'opt', + single: 'single', + singleOpt: 'singleOpt', + multi: 'multi1,multi2', + }); + + // Minimal values provided + expect( + createExtensionTester( + subject.override({ + factory(originalFactory) { + return originalFactory({ + inputs: { + single: [testDataRef1('single')], + multi: [[testDataRef1('multi1')], [testDataRef1('multi2')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'none', + single: 'single', + singleOpt: 'none', + multi: 'multi1,multi2', + }); + + // Forward inputs directly + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }); + + // Forward inputs separately + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: inputs.multi, + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }); + + // Overriding based on original input + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + single: [ + testDataRef1(`override-${inputs.single.get(testDataRef1)}`), + testDataRef2('new-singleOpt'), + ], + multi: inputs.multi.map(i => [ + testDataRef1(`override-${i.get(testDataRef1)}`), + ]), + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'none', + single: 'override-orig-single', + singleOpt: 'new-singleOpt', + multi: 'override-orig-multi1,override-orig-multi2', + }); + + // Mismatched input override length + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + ...inputs, + multi: [[testDataRef1('multi1')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, + ); + + // Required input not provided + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + ...inputs, + single: [testDataRef2('singleOpt')], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', Missing required data values for 'test1'"`, + ); + + // Wrong value provided + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory) { + return originalFactory({ + inputs: { + // @ts-expect-error + opt: [testDataRef2('opt')], + // @ts-expect-error + single: [testDataRef1('single'), outputRef({})], + multi: [ + // @ts-expect-error + [testDataRef2('multi1')], + ], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', Invalid data value provided, 'test2' was not declared"`, + ); + }); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index f2d3d906ab..55d71e0d2f 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -17,7 +17,11 @@ import { AppNode } from '../apis'; import { PortableSchema, createSchemaFromZod } from '../schema'; import { Expand } from '../types'; -import { createDataContainer } from './createExtensionBlueprint'; +import { + ResolveInputValueOverrides, + createDataContainer, + resolveInputOverrides, +} from './createExtensionBlueprint'; import { AnyExtensionDataRef, ExtensionDataRef, @@ -259,7 +263,7 @@ export interface ExtensionDefinition< factory( originalFactory: (context?: { config?: TConfig; - inputs?: Expand>; + inputs?: ResolveInputValueOverrides; }) => ExtensionDataContainer, context: { node: AppNode; @@ -558,7 +562,7 @@ export function createExtension< ReturnType >; }; - inputs?: Expand>; + inputs?: ResolveInputValueOverrides; }) => ExtensionDataContainer, context: { node: AppNode; @@ -646,14 +650,19 @@ export function createExtension< ReturnType >; }; - inputs?: Expand>; + inputs?: ResolveInputValueOverrides; }): ExtensionDataContainer => { return createDataContainer( newOptions.factory({ node, config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + inputs: resolveInputOverrides( + newOptions.inputs, + inputs, + innerContext?.inputs, + ) as any, // TODO: Might be able to improve this once legacy inputs are gone }) as Iterable, + newOptions.output, ); }, { diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 88f3a6e9dd..cd6c0d6968 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -78,7 +78,7 @@ export type CreateExtensionBlueprintOptions< } & VerifyExtensionFactoryOutput; /** @ignore */ -type ResolveInputValueOverrides< +export type ResolveInputValueOverrides< TInputs extends { [inputName in string]: ExtensionInput< AnyExtensionDataRef, From 3d7d38d94f3ef904062461ebbf9ef1c638d8a0ad Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 11 Aug 2024 20:04:48 +0200 Subject: [PATCH 146/393] frontend-plugin-api: clean up error messages for input overrides Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 6 +++--- .../src/wiring/createExtensionBlueprint.test.tsx | 10 +++++----- .../src/wiring/createExtensionBlueprint.ts | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 9d2aff5d99..d6e2599138 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -969,7 +969,7 @@ describe('createExtension', () => { .add(multi2Ext) .data(outputRef), ).toThrowErrorMatchingInlineSnapshot( - `"Failed to instantiate extension 'subject', Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, + `"Failed to instantiate extension 'subject', override data provided for input 'multi' must match the length of the original inputs"`, ); // Required input not provided @@ -992,7 +992,7 @@ describe('createExtension', () => { .add(multi2Ext) .data(outputRef), ).toThrowErrorMatchingInlineSnapshot( - `"Failed to instantiate extension 'subject', Missing required data values for 'test1'"`, + `"Failed to instantiate extension 'subject', missing required extension data value(s) 'test1'"`, ); // Wrong value provided @@ -1021,7 +1021,7 @@ describe('createExtension', () => { .add(multi2Ext) .data(outputRef), ).toThrowErrorMatchingInlineSnapshot( - `"Failed to instantiate extension 'subject', Invalid data value provided, 'test2' was not declared"`, + `"Failed to instantiate extension 'subject', extension data 'test2' was provided but not declared"`, ); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index efca17459b..508000acbb 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -462,7 +462,7 @@ describe('createExtensionBlueprint', () => { mockParentInputs, ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid override provided for input 'multi', when overriding the input data the length must match the original input data"`, + `"override data provided for input 'multi' must match the length of the original inputs"`, ); // Required input not provided @@ -484,7 +484,7 @@ describe('createExtensionBlueprint', () => { mockParentInputs, ), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required data values for 'test1'"`, + `"missing required extension data value(s) 'test1'"`, ); // Wrong value provided @@ -512,7 +512,7 @@ describe('createExtensionBlueprint', () => { mockParentInputs, ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid data value provided, 'test2' was not declared"`, + `"extension data 'test2' was provided but not declared"`, ); // Forwarding entire inputs object @@ -706,7 +706,7 @@ describe('createExtensionBlueprint', () => { }).makeWithOverrides({ factory: orig => orig({}) }), ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid data value provided, 'test2' was not declared"`, + `"extension data 'test2' was provided but not declared"`, ); expect(() => @@ -722,7 +722,7 @@ describe('createExtensionBlueprint', () => { }).makeWithOverrides({ factory: orig => orig({}) }), ), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required data values for 'test1'"`, + `"missing required extension data value(s) 'test1'"`, ); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index cd6c0d6968..74d4426207 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -269,7 +269,7 @@ export function createDataContainer( if (verifyRefs) { if (!verifyRefs.delete(output.id)) { throw new Error( - `Invalid data value provided, '${output.id}' was not declared`, + `extension data '${output.id}' was provided but not declared`, ); } } @@ -281,7 +281,7 @@ export function createDataContainer( Array.from(verifyRefs.values()).filter(ref => !ref.config.optional); if (remainingRefs && remainingRefs.length > 0) { throw new Error( - `Missing required data values for '${remainingRefs + `missing required extension data value(s) '${remainingRefs .map(ref => ref.id) .join(', ')}'`, ); @@ -339,7 +339,7 @@ export function resolveInputOverrides( ); if (!originalInput) { throw new Error( - `A data override was provided for input '${name}', but no original input was present.`, + `attempted to override data of input '${name}' but it is not present in the original inputs`, ); } newInputs[name] = Object.assign(providedContainer, { @@ -350,12 +350,12 @@ export function resolveInputOverrides( const originalInput = expectArray(inputs[name]); if (!Array.isArray(providedData)) { throw new Error( - `Invalid override provided for input '${name}', expected an array`, + `override data provided for input '${name}' must be an array`, ); } if (originalInput.length !== providedData.length) { throw new Error( - `Invalid override provided for input '${name}', when overriding the input data the length must match the original input data`, + `override data provided for input '${name}' must match the length of the original inputs`, ); } newInputs[name] = providedData.map((data, i) => { From 553015651769c44c750a8b6012178f4231c3f8dd Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Sun, 11 Aug 2024 11:46:59 +0100 Subject: [PATCH 147/393] Update .changeset/five-mangos-agree.md Co-authored-by: Ben Lambert Signed-off-by: Brian Fletcher --- .changeset/five-mangos-agree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/five-mangos-agree.md b/.changeset/five-mangos-agree.md index 1ad9b6b04c..f5ecb2891b 100644 --- a/.changeset/five-mangos-agree.md +++ b/.changeset/five-mangos-agree.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch --- -Add abilty to set the initial commit message when initializing a repository using the scaffolder action. +Add ability to set the initial commit message when initializing a repository using the scaffolder action. From a40904dc7d38419ed9a693992b73026634d726fc Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 12 Aug 2024 09:33:13 +0200 Subject: [PATCH 148/393] fix: add link to mui docs + fix typo in changeset Signed-off-by: Julien --- .changeset/perfect-cars-jam.md | 2 +- plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/perfect-cars-jam.md b/.changeset/perfect-cars-jam.md index a22791034a..45166ac823 100644 --- a/.changeset/perfect-cars-jam.md +++ b/.changeset/perfect-cars-jam.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': minor --- -Changed the way to display entities in `MyGroupsPicker` to use entityPresentationApi and make it consistent across scaffolder pickers +Changed the way to display entities in `MyGroupsPicker` to use `entityPresentationApi` and make it consistent across scaffolder pickers diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx index b2bd611cec..2fad510918 100644 --- a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx +++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx @@ -22,6 +22,7 @@ const renderRow = (props: ListChildComponentProps) => { return React.cloneElement(data[index], { style }); }; +// Context needed to keep Autocomplete working correctly : https://v4.mui.com/components/autocomplete/#virtualization const OuterElementContext = React.createContext({}); const OuterElementType = React.forwardRef((props, ref) => { From 11ffdbd7f3e4cf516a682ac2eeb029b62875ac85 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 12 Aug 2024 09:34:37 +0200 Subject: [PATCH 149/393] fix: add correct link to mui docs for Virtualization Signed-off-by: Julien --- plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx index 2fad510918..61e96cd7a9 100644 --- a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx +++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx @@ -22,7 +22,7 @@ const renderRow = (props: ListChildComponentProps) => { return React.cloneElement(data[index], { style }); }; -// Context needed to keep Autocomplete working correctly : https://v4.mui.com/components/autocomplete/#virtualization +// Context needed to keep Autocomplete working correctly : https://v4.mui.com/components/autocomplete/#Virtualize.tsx const OuterElementContext = React.createContext({}); const OuterElementType = React.forwardRef((props, ref) => { From c60b3b7af82103cc32606e021680a0213aa3efcd Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 26 Mar 2024 15:28:07 +0100 Subject: [PATCH 150/393] chore: creating the initial bep Signed-off-by: blam --- .../README.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 beps/NNNN-scaffolder-templating-in-parameters/README.md diff --git a/beps/NNNN-scaffolder-templating-in-parameters/README.md b/beps/NNNN-scaffolder-templating-in-parameters/README.md new file mode 100644 index 0000000000..49ec16d8ed --- /dev/null +++ b/beps/NNNN-scaffolder-templating-in-parameters/README.md @@ -0,0 +1,100 @@ +--- +title: Supporting templating syntax in `parameters` +status: provisional +authors: + - '@benjdlambert' +owners: + - '@benjdlambert' + - '@backstage/scaffolder-maintainers' +project-areas: + - scaffolder +creation-date: 2024-03-26 +--- + + + +# BEP: + + + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + + + +## Motivation + + + +### Goals + + + +### Non-Goals + + + +## Proposal + + + +## Design Details + + + +## Release Plan + + + +## Dependencies + + + +## Alternatives + + From 8f6356169c85bb6f14b8ae0d5fdeb1838df5dbe1 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 28 Mar 2024 17:45:53 +0100 Subject: [PATCH 151/393] chore: starting to write some more for the bep Signed-off-by: blam Signed-off-by: blam --- .../README.md | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/beps/NNNN-scaffolder-templating-in-parameters/README.md b/beps/NNNN-scaffolder-templating-in-parameters/README.md index 49ec16d8ed..71f92e9b64 100644 --- a/beps/NNNN-scaffolder-templating-in-parameters/README.md +++ b/beps/NNNN-scaffolder-templating-in-parameters/README.md @@ -1,5 +1,5 @@ --- -title: Supporting templating syntax in `parameters` +title: Supporting templating syntax in `parameters` schema status: provisional authors: - '@benjdlambert' @@ -17,11 +17,11 @@ creation-date: 2024-03-26 When editing BEPs, aim for tightly-scoped, single-topic PRs to keep discussions focused. If you disagree with what is already in a document, open a new PR with suggested changes. --> -# BEP: +# BEP: Supporting templating syntax in `parameters` schema -[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) +[**Discussion Issue**](https://github.com/backstage/backstage/issues/16275) - [Summary](#summary) - [Motivation](#motivation) @@ -39,6 +39,30 @@ When editing BEPs, aim for tightly-scoped, single-topic PRs to keep discussions The summary of the BEP is a few paragraphs long and give a high-level overview of the features to be implemented. It should be possible to read *only* the summary and understand what the BEP is proposing to accomplish and what impact it has for users. --> +This BEP proposes to add support for templating syntax in the `parameters` schema of a scaffolder template. +This will allow users to define properties in the JSON Schema which are templated from current values that have been collected from the user already. +This can be useful when you want to use a value that has already been collected as a default value in another field. + +For example: + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: my-template +spec: + parameters: + - title: Some input + description: Get some info from the user + properties: + name: + type: string + default: Test + description: + type: string + default: ${{ parameters.name or "unknown" }}-description +``` + ## Motivation +Inclusive of the initial RFC there's been a swarm of issues that are requesting this feature, and we want to align on the implementation and design of this feature. + +See the following: + +- https://github.com/backstage/backstage/issues/16275 +- https://github.com/backstage/backstage/pull/23283 +- https://github.com/backstage/backstage/issues/19597 +- https://github.com/backstage/backstage/issues/20533 +- https://github.com/backstage/backstage/pull/17746 + +There's some ideas for introducing a templating syntax for both templating into the `parameters` schema, and also being able to pass through some templating strings to underlying field extensions that can use those templating strings. +We want to align here so that we're not going to have those conflict or compete, and create a standard for how to achieve templating in both circumstances. + ### Goals +- This BEP will settle the implementation for the templating of fields into the JSON Schema in the `parameters` section in the scaffolder templates. +- This BEP will settle how to pass through templating strings to underlying field extensions in a non-conflicting way. + ### Non-Goals +The proposal is to be able to decorate the template schema server side with a context and use that to drive the form rendering client side. + +We can extend the `/parameter-schema` endpoint to accept a `formData` context query parameter which will be a JSON object of the current `formData` state. + +```diff +export interface ScaffolderApi { + getTemplateParameterSchema( + templateRef: string, ++ formData?: string, + ): Promise; +} +``` + +```diff + router + .get( + '/v2/templates/:namespace/:kind/:name/parameter-schema', + async (req, res) => { + const credentials = await httpAuth.credentials(req); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + const template = await authorizeTemplate( + req.params, + token, + credentials, + ); + + const parameters = [template.spec.parameters ?? []].flat(); ++ const secureTemplater = await SecureTemplater.loadRenderer({ ++ templateFilters: { ++ ...createDefaultFilters({ integrations }), ++ ...additionalTemplateFilters, ++ }, ++ templateGlobals: additionalTemplateGlobals, ++ }); ++ ++ const templatedParameters = parameters.map(parameter => ++ renderTemplateString( ++ parameter, ++ { ++ parameters: req.query.formData, ++ }, ++ secureTemplater, ++ logger, ++ ), ++ ); +``` + +You can see a quick implementation of this in this [branch](https://github.com/backstage/backstage/compare/master...blam/templating-in-parameters) ## Design Details - +There's a slight issue with the implementation of the `react-jsonschema-form`, which makes things like live updating on things like the `default` field slightly more difficult. +Currently, on first render, the default value is populated and then stored in the `formData` object or the current state, and the default value is never re-evaluated again at a later stage. + +This means that if end users are wanting to set default values with `${{ parameters.myOtherProperty }}`, then they would need to ensure that they are on different steps in the form +as the form would need to be re-rendered, and for performance reasons, we don't want to re-render the form on every `formData` update. + +We could fix this, by implementing custom logic for when the `parameter-schema` is updated, if the updated field is in a `default: *` field, then we replace the previous value with the new value in the `formData` automatically. +This is a pretty ugly workaround, but maybe the only option we have. Also at this point, pretty unsure if this affects any other parts of the `JSONSchema`, and we would also have to implement it for those fields if they exist. + +Perhaps we just accept this as a limitation, and document it as such. ## Release Plan @@ -125,6 +175,8 @@ This section should describe the rollout process for any new features. It must t If there is any particular feedback to be gathered during the rollout, this should be described here as well. --> +This change is backwards compatible, and can be released in a minor release. There's no breaking changes to worry about here. + ## Dependencies + +#### Templating client side + +- This could lead to confusion as `filters` such as `parseRepoUrl` and `pick` and any custom filters which you define in the backend would not be available in the client side. +- Also with the limitations of the `default` value being updated only on first render and never re-evaluated, there's no performance benefit of doing things client side anymore. From 8db1ba1508ee0db37cb8c2690e59df43a018adee Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 09:49:25 +0200 Subject: [PATCH 155/393] chore: give the bep a number and feelings Signed-off-by: blam --- .../README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename beps/{NNNN-scaffolder-templating-in-parameters => 0009-scaffolder-templating-in-parameters}/README.md (100%) diff --git a/beps/NNNN-scaffolder-templating-in-parameters/README.md b/beps/0009-scaffolder-templating-in-parameters/README.md similarity index 100% rename from beps/NNNN-scaffolder-templating-in-parameters/README.md rename to beps/0009-scaffolder-templating-in-parameters/README.md From 1c94fb14db0fd4df52356636f54dcafd5fd64c92 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jul 2024 08:52:31 +0200 Subject: [PATCH 156/393] chore: fix Signed-off-by: blam --- .../README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/beps/0009-scaffolder-templating-in-parameters/README.md b/beps/0009-scaffolder-templating-in-parameters/README.md index 450538ab8a..d5fc78497c 100644 --- a/beps/0009-scaffolder-templating-in-parameters/README.md +++ b/beps/0009-scaffolder-templating-in-parameters/README.md @@ -167,6 +167,20 @@ This is a pretty ugly workaround, but maybe the only option we have. Also at thi Perhaps we just accept this as a limitation, and document it as such. +Templating for `errorMessages` has been solved by using the `ajv-errors` library https://github.com/backstage/backstage/pull/25624, you can see more about [backrefs and pointers here](https://ajv.js.org/packages/ajv-errors.html). Any other template strings that will be passed through the underlying components and to be left untemplated should be encapsulated with options instead of passing through raw strings. The below example illustrates an `entityAndName` format, which under the hood, might do something like `${{ parameters.entity }} - ${{ parameters.name }}`, but this implementation never leaks out to the templating language. + +```yaml +parameters: + properties: + ... + description: + type: string + default: Test-description + ui:field: CustomDisplayField + ui:options: + format: entityAndName +``` + ## Release Plan -#### Templating client side +### Templating client side - This could lead to confusion as `filters` such as `parseRepoUrl` and `pick` and any custom filters which you define in the backend would not be available in the client side. - Also with the limitations of the `default` value being updated only on first render and never re-evaluated, there's no performance benefit of doing things client side anymore. + +### Accept limitation of the `default` field + +Rather than using a workaround to support re-evaluating the `default` field, we could instead accept it as a limitation, and document it as such. + +This is not desirable, as it is likely a very common use-case to want to template the `default` field, leading to a poor template creation experience. From 87b6e03693198482f257280297320bcef9236742 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 12:08:37 +0200 Subject: [PATCH 187/393] docs/frontend-system: make space in architecture docs Signed-off-by: Patrik Oldsberg --- .../architecture/{01-index.md => 00-index.md} | 0 .../architecture/{02-app.md => 10-app.md} | 6 +++--- .../architecture/{04-plugins.md => 15-plugins.md} | 0 .../{03-extensions.md => 20-extensions.md} | 4 ++-- ...sion-overrides.md => 25-extension-overrides.md} | 2 +- .../{08-references.md => 30-references.md} | 0 .../{06-utility-apis.md => 33-utility-apis.md} | 2 +- .../architecture/{07-routes.md => 36-routes.md} | 0 ...08-naming-patterns.md => 50-naming-patterns.md} | 0 docs/frontend-system/building-apps/01-index.md | 4 ++-- .../building-apps/03-built-in-extensions.md | 2 +- docs/frontend-system/building-apps/08-migrating.md | 14 +++++++------- docs/frontend-system/building-plugins/01-index.md | 14 +++++++------- .../frontend-system/building-plugins/02-testing.md | 2 +- .../building-plugins/03-extension-types.md | 2 +- .../building-plugins/04-built-in-data-refs.md | 2 +- docs/frontend-system/utility-apis/01-index.md | 2 +- docs/frontend-system/utility-apis/02-creating.md | 2 +- .../frontend-system/utility-apis/04-configuring.md | 4 ++-- docs/plugins/composability.md | 4 ++-- microsite/sidebars.json | 6 +++--- 21 files changed, 36 insertions(+), 36 deletions(-) rename docs/frontend-system/architecture/{01-index.md => 00-index.md} (100%) rename docs/frontend-system/architecture/{02-app.md => 10-app.md} (95%) rename docs/frontend-system/architecture/{04-plugins.md => 15-plugins.md} (100%) rename docs/frontend-system/architecture/{03-extensions.md => 20-extensions.md} (99%) rename docs/frontend-system/architecture/{05-extension-overrides.md => 25-extension-overrides.md} (99%) rename docs/frontend-system/architecture/{08-references.md => 30-references.md} (100%) rename docs/frontend-system/architecture/{06-utility-apis.md => 33-utility-apis.md} (97%) rename docs/frontend-system/architecture/{07-routes.md => 36-routes.md} (100%) rename docs/frontend-system/architecture/{08-naming-patterns.md => 50-naming-patterns.md} (100%) diff --git a/docs/frontend-system/architecture/01-index.md b/docs/frontend-system/architecture/00-index.md similarity index 100% rename from docs/frontend-system/architecture/01-index.md rename to docs/frontend-system/architecture/00-index.md diff --git a/docs/frontend-system/architecture/02-app.md b/docs/frontend-system/architecture/10-app.md similarity index 95% rename from docs/frontend-system/architecture/02-app.md rename to docs/frontend-system/architecture/10-app.md index 3b839d4232..532971d124 100644 --- a/docs/frontend-system/architecture/02-app.md +++ b/docs/frontend-system/architecture/10-app.md @@ -33,13 +33,13 @@ const rootEl = document.getElementById('root')!; ReactDOM.createRoot(rootEl).render(app); ``` -We call `createApp` to create a new app instance, which is responsible for wiring together all of the features that we provide to the app. It also provides a set of built-in [Extensions](./03-extensions.md) that help build out the foundations of the app, as well as defaults for many other systems such as [Utility API](./06-utility-apis.md) implementations, components, icons, themes, and how to load configuration. No real work is done at the point of creating the app though, it's all deferred to the rendering of the element returned from `app.createRoot()`. +We call `createApp` to create a new app instance, which is responsible for wiring together all of the features that we provide to the app. It also provides a set of built-in [Extensions](./20-extensions.md) that help build out the foundations of the app, as well as defaults for many other systems such as [Utility API](./33-utility-apis.md) implementations, components, icons, themes, and how to load configuration. No real work is done at the point of creating the app though, it's all deferred to the rendering of the element returned from `app.createRoot()`. -It is possible to explicitly install features when creating the app, although typically these will instead be discovered automatically which we'll explore later on. Nevertheless these features are what build out the actual functionality of the app by providing [Extensions](./03-extensions.md). These extensions are wired together by the app into a tree structure known as the app extension tree. Each node in this tree receives data from its child nodes, and passes along data to its parent. The following diagram illustrates the shape of a small app extension tree. +It is possible to explicitly install features when creating the app, although typically these will instead be discovered automatically which we'll explore later on. Nevertheless these features are what build out the actual functionality of the app by providing [Extensions](./20-extensions.md). These extensions are wired together by the app into a tree structure known as the app extension tree. Each node in this tree receives data from its child nodes, and passes along data to its parent. The following diagram illustrates the shape of a small app extension tree. ![frontend system app structure diagram](../../assets/frontend-system/architecture-app.drawio.svg) -Each node in this tree is an extension with a parent node and children. The colored shapes represent extension data inputs and output, where each color is one unique type of data. You can see that there are both extensions that output data that is ignored by the parent, as well as extensions that accept inputs but do not have any children. There are a couple of different tools at your disposal when creating and extension that lets you define different requirements for your inputs and output, which we will cover in greater details in the [Extensions](./03-extensions.md) section. +Each node in this tree is an extension with a parent node and children. The colored shapes represent extension data inputs and output, where each color is one unique type of data. You can see that there are both extensions that output data that is ignored by the parent, as well as extensions that accept inputs but do not have any children. There are a couple of different tools at your disposal when creating and extension that lets you define different requirements for your inputs and output, which we will cover in greater details in the [Extensions](./20-extensions.md) section. A common type of data that is shared between extensions is React elements and components. These can in turn be rendered by each other in their own React components, which ends up forming a parallel tree of React components that is similar in shape to that of the app extension tree. At the top of the app extension tree is a built-in root extension that among other things outputs a React element. This element also ends up being the root of the parallel React tree, and is rendered by the React element returned by `app.createRoot()`. diff --git a/docs/frontend-system/architecture/04-plugins.md b/docs/frontend-system/architecture/15-plugins.md similarity index 100% rename from docs/frontend-system/architecture/04-plugins.md rename to docs/frontend-system/architecture/15-plugins.md diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/20-extensions.md similarity index 99% rename from docs/frontend-system/architecture/03-extensions.md rename to docs/frontend-system/architecture/20-extensions.md index 2f3daba6f8..eec9947c97 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -8,7 +8,7 @@ description: Frontend extensions > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -As mentioned in the [previous section](./02-app.md), Backstage apps are built up from a tree of extensions. This section will go into more detail about what extensions are, how to create and use them, and how to create your own extensibility patterns. +As mentioned in the [previous section](./10-app.md), Backstage apps are built up from a tree of extensions. This section will go into more detail about what extensions are, how to create and use them, and how to create your own extensibility patterns. ## Extension Structure @@ -317,7 +317,7 @@ The `id` of the extension is then build out of `namespace`, `name` & `kind` like id: kind:namespace/name ``` -For more information on naming of extension refer to the [naming patterns documentation](./08-naming-patterns.md). +For more information on naming of extension refer to the [naming patterns documentation](./50-naming-patterns.md). ### Extension Creators in libraries diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md similarity index 99% rename from docs/frontend-system/architecture/05-extension-overrides.md rename to docs/frontend-system/architecture/25-extension-overrides.md index 41702e6823..42a7f365a1 100644 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -89,7 +89,7 @@ Note that it can still be a good idea to split your overrides out into separate To override an extension that is provided by a plugin, you need to provide a new extension that has the same ID as the existing extension. That is, all kind, namespace, and name options must match the extension you want to replace. This means that you typically need to provide an explicit `namespace` when overriding extensions from a plugin. :::info -We recommend that plugin developers share the extension IDs in their plugin documentation, but usually you can infer the ID by following the [naming patterns](./08-naming-patterns.md) documentation. +We recommend that plugin developers share the extension IDs in their plugin documentation, but usually you can infer the ID by following the [naming patterns](./50-naming-patterns.md) documentation. ::: ### Example diff --git a/docs/frontend-system/architecture/08-references.md b/docs/frontend-system/architecture/30-references.md similarity index 100% rename from docs/frontend-system/architecture/08-references.md rename to docs/frontend-system/architecture/30-references.md diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/33-utility-apis.md similarity index 97% rename from docs/frontend-system/architecture/06-utility-apis.md rename to docs/frontend-system/architecture/33-utility-apis.md index 49e5854b65..c20936f819 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/33-utility-apis.md @@ -10,7 +10,7 @@ description: Utility APIs ## Overview -Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config. +Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/20-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config. A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. diff --git a/docs/frontend-system/architecture/07-routes.md b/docs/frontend-system/architecture/36-routes.md similarity index 100% rename from docs/frontend-system/architecture/07-routes.md rename to docs/frontend-system/architecture/36-routes.md diff --git a/docs/frontend-system/architecture/08-naming-patterns.md b/docs/frontend-system/architecture/50-naming-patterns.md similarity index 100% rename from docs/frontend-system/architecture/08-naming-patterns.md rename to docs/frontend-system/architecture/50-naming-patterns.md diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index ea7fe20948..bb09fb6907 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -66,7 +66,7 @@ Linking routes from different plugins requires this configuration. You can do th ### Enable feature discovery -Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/02-app.md#feature-discovery) for more details. +Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/10-app.md#feature-discovery) for more details. :::warning Remember that package extensions that are not auto-discovered must be manually added to the application when creating an app. See [features](#install-features-manually) for more details. @@ -99,7 +99,7 @@ export default app.createRoot(); ``` :::info -You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/05-extension-overrides.md) documentation. +You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/25-extension-overrides.md) documentation. ::: ### Using an async features loader diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md index a5e6476eb0..5f74100df0 100644 --- a/docs/frontend-system/building-apps/03-built-in-extensions.md +++ b/docs/frontend-system/building-apps/03-built-in-extensions.md @@ -24,7 +24,7 @@ Be careful when disabling built-in extensions, as there may be other extensions ## Override built-in extensions -You can override any built-in extension whenever their customizations, whether through configuration or input, do not meet a use case for your Backstage instance. Check out [this](../architecture/05-extension-overrides.md) documentation on how to override application extensions. +You can override any built-in extension whenever their customizations, whether through configuration or input, do not meet a use case for your Backstage instance. Check out [this](../architecture/25-extension-overrides.md) documentation on how to override application extensions. :::warning Be aware there could be some implementation requirements to properly override an built-in extension, such as using same apis and do not remove inputs or configurations otherwise you can cause a side effect in other parts of the system that expects same minimal behavior. diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 716e0da772..5d92478661 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -23,7 +23,7 @@ import { createApp } from '@backstage/frontend-app-api'; This immediate switch will lead to a lot of breakages that we need to fix. -Let's start by addressing the change to `app.createRoot(...)`, which no longer accepts any arguments. This represents a fundamental change that the new frontend system introduces. In the old system the app element tree that you passed to `app.createRoot(...)` was the primary way that you installed and configured plugins and features in your app. In the new system this is instead replaced by extensions that are wired together into an extension tree. Much more responsibility has now been shifted to plugins, for example you no longer have to manually provide the route path for each plugin page, but instead only configure it if you want to override the default. For more information on how the new system works, see the [architecture](../architecture/01-index.md) section. +Let's start by addressing the change to `app.createRoot(...)`, which no longer accepts any arguments. This represents a fundamental change that the new frontend system introduces. In the old system the app element tree that you passed to `app.createRoot(...)` was the primary way that you installed and configured plugins and features in your app. In the new system this is instead replaced by extensions that are wired together into an extension tree. Much more responsibility has now been shifted to plugins, for example you no longer have to manually provide the route path for each plugin page, but instead only configure it if you want to override the default. For more information on how the new system works, see the [architecture](../architecture/00-index.md) section. Given that the app element tree is most of what builds up the app, it's likely also going to be the majority of the migration effort. In order to make the migration as smooth as possible we have provided a helper that lets you convert an existing app element tree into plugins that you can install in a new app. This in turn allows for a gradual migration of individual plugins, rather than needing to migrate the entire app structure at once. @@ -95,7 +95,7 @@ At this point the contents of your app should be past the initial migration stag ## Migrating `createApp` Options -Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension creator](../architecture/03-extensions.md#extension-creators) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/05-extension-overrides.md#create-standalone-extensions) section for more information. +Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension creator](../architecture/20-extensions.md#extension-creators) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/25-extension-overrides.md#create-standalone-extensions) section for more information. For example, assuming you have a `lightTheme` extension that you want to add to your app, you can use the following: @@ -176,7 +176,7 @@ createApp({ }); ``` -Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/02-app.md#feature-discovery) is enabled. +Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/10-app.md#feature-discovery) is enabled. ```yaml title="in app-config.yaml" app: @@ -219,7 +219,7 @@ createPlugin({ Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information. -The `Router` component is now a built-in extension that you can [override](../architecture/05-extension-overrides.md) using `createRouterExtension`. +The `Router` component is now a built-in extension that you can [override](../architecture/25-extension-overrides.md) using `createRouterExtension`. The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead. @@ -341,7 +341,7 @@ const app = createApp({ ### `bindRoutes` -Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/07-routes.md#binding-external-route-references) for more information. +Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/36-routes.md#binding-external-route-references) for more information. Note that if you are binding routes from a legacy plugin that was converted using `convertLegacyApp`, you will need to use the `convertLegacyRouteRefs` and/or `convertLegacyRouteRef` to convert the routes to be compatible with the new system. @@ -470,7 +470,7 @@ const routes = ( ); ``` -If you are using [app feature discovery](../architecture/02-app.md#feature-discovery) the installation step is simple, it's already done! The new version of the scaffolder plugin was already discovered and present in the app, it was simply disabled because the plugin created from the legacy route had higher priority. If you do not use feature discovery, you will instead need to manually install the new scaffolder plugin in your app through the `features` option of `createApp`. +If you are using [app feature discovery](../architecture/10-app.md#feature-discovery) the installation step is simple, it's already done! The new version of the scaffolder plugin was already discovered and present in the app, it was simply disabled because the plugin created from the legacy route had higher priority. If you do not use feature discovery, you will instead need to manually install the new scaffolder plugin in your app through the `features` option of `createApp`. Continue this process for each of your legacy routes until you have migrated all of them. For any plugin with additional extensions installed as children of the `Route`, refer to the plugin READMEs for more detailed instructions. For the entity pages, refer to the [separate section](#entity-pages). @@ -482,7 +482,7 @@ The entity pages are typically defined in `packages/app/src/components/catalog` New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box. -Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/05-extension-overrides.md): +Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/25-extension-overrides.md): ```tsx const nav = createExtension({ diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index f8d80f9382..618e660f2b 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -8,9 +8,9 @@ description: Building frontend plugins using the new frontend system > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -This section covers how to build your own frontend [plugins](../architecture/04-plugins.md) and -[overrides](../architecture/05-extension-overrides.md). They are sometimes collectively referred to as -frontend _features_, and what you install to build up a Backstage frontend [app](../architecture/02-app.md). +This section covers how to build your own frontend [plugins](../architecture/15-plugins.md) and +[overrides](../architecture/25-extension-overrides.md). They are sometimes collectively referred to as +frontend _features_, and what you install to build up a Backstage frontend [app](../architecture/10-app.md). ## Creating a new plugin @@ -43,13 +43,13 @@ export { examplePlugin as default } from './plugin'; Note that we export the plugin as the default export of our package from `src/index.ts`. This is important, as it is how users of our plugin are able to seamlessly install the plugin package in a Backstage app without having to reference the plugin instance through code. -The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camel case version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/08-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. +The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camel case version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/50-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. ## Adding extensions -The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/03-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. +The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/20-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. -To create a new extension you typically use pre-defined [extension creators](../architecture/03-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/07-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. +To create a new extension you typically use pre-defined [extension creators](../architecture/20-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. ```tsx title="in src/routes.ts" import { createRouteRef } from '@backstage/frontend-plugin-api'; @@ -106,7 +106,7 @@ export const examplePlugin = createPlugin({ What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component, in this case the highlighted `ExamplePage`, can be arbitrarily complex. It can be anything from a single simple information page, to a full-blown application with multiple sub-pages. -We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/07-routes.md#external-route-references) section. +We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/36-routes.md#external-route-references) section. ## Utility APIs diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md index 26c9503c6d..f665ed0f0c 100644 --- a/docs/frontend-system/building-plugins/02-testing.md +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -41,7 +41,7 @@ describe('Entity details component', () => { }); ``` -To mock [Utility APIs](../architecture/06-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API: +To mock [Utility APIs](../architecture/33-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API: ```tsx import React from 'react'; diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md index 1f3baaa710..c7c2e2ab1f 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -8,7 +8,7 @@ description: Extension types provided by the frontend system and core features > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -This section covers many of the [extension types](../architecture/03-extensions.md#extension-creators) available at your disposal when building Backstage frontend plugins. +This section covers many of the [extension types](../architecture/20-extensions.md#extension-creators) available at your disposal when building Backstage frontend plugins. ## Built-in extension types diff --git a/docs/frontend-system/building-plugins/04-built-in-data-refs.md b/docs/frontend-system/building-plugins/04-built-in-data-refs.md index dacbbf30f1..d13e734279 100644 --- a/docs/frontend-system/building-plugins/04-built-in-data-refs.md +++ b/docs/frontend-system/building-plugins/04-built-in-data-refs.md @@ -8,7 +8,7 @@ description: Configuring or overriding built-in extension data references > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -To have a better understanding of extension data references please read [the corresponding architecture section](../architecture/03-extensions.md#extension-data) first. +To have a better understanding of extension data references please read [the corresponding architecture section](../architecture/20-extensions.md#extension-data) first. ## Built-in extension data references diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md index 37120a5152..07f7dcc2a8 100644 --- a/docs/frontend-system/utility-apis/01-index.md +++ b/docs/frontend-system/utility-apis/01-index.md @@ -8,7 +8,7 @@ description: Working with Utility APIs in the New Frontend System > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -As described [in the architecture section](../architecture/06-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. +As described [in the architecture section](../architecture/33-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/20-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. ## Creating utility APIs diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index fcb7ff1a65..77caaf55f0 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -86,7 +86,7 @@ For illustration we make a skeleton implementation class and the API extension a The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function. -The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. Check out [the naming patterns doc](../architecture/08-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. +The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. Check out [the naming patterns doc](../architecture/50-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. ## Adding configurability diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index 7acf23decf..d22e10011b 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -12,7 +12,7 @@ Utility APIs are extensions and can therefore optionally be amended with configu ## Configuring -To configure your Utility API extension, first you'll need to know its ID. That ID is formed from the API ref ID; check [the naming patterns docs](../architecture/08-naming-patterns.md) for details. +To configure your Utility API extension, first you'll need to know its ID. That ID is formed from the API ref ID; check [the naming patterns docs](../architecture/50-naming-patterns.md) for details. Our example work API from [the creating section](./02-creating.md) would have the ID `api:plugin.example.work`. You configure it and all other extensions under the `app.extensions` section of your app-config. @@ -37,7 +37,7 @@ Well written input-enabled extension often have extension creator functions that ## Replacing a Utility API implementation -Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/05-extension-overrides.md). +Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/25-extension-overrides.md). ```tsx title="in your app" /* highlight-add-start */ diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 1a9c0babf9..a0e0e2d5e6 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -329,7 +329,7 @@ You can also use static configuration to bind routes, removing the need to make changes to the app code. It does however mean that you won't get type safety when binding routes and compile-time validation of the bindings. Static configuration of route bindings is done under the `app.routes.bindings` key in -`app-config.yaml`. It works the same way as [route bindings in the new frontend system](../frontend-system/architecture/07-routes.md#binding-external-route-references), +`app-config.yaml`. It works the same way as [route bindings in the new frontend system](../frontend-system/architecture/36-routes.md#binding-external-route-references), for example: ```yaml @@ -342,7 +342,7 @@ app: ### Default Targets for External Route References Following the `1.28` release of Backstage you can now define default targets for -external route references. They work the same way as [default targets in the new frontend system](../frontend-system/architecture/07-routes.md#default-targets-for-external-route-references), +external route references. They work the same way as [default targets in the new frontend system](../frontend-system/architecture/36-routes.md#default-targets-for-external-route-references), for example: ```ts diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ddccb14c91..be17d76e03 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -452,13 +452,13 @@ "items": [ "frontend-system/architecture/index", "frontend-system/architecture/app", - "frontend-system/architecture/extensions", "frontend-system/architecture/plugins", + "frontend-system/architecture/extensions", "frontend-system/architecture/extension-overrides", + "frontend-system/architecture/references", "frontend-system/architecture/utility-apis", "frontend-system/architecture/routes", - "frontend-system/architecture/naming-patterns", - "frontend-system/architecture/references" + "frontend-system/architecture/naming-patterns" ] }, { From d61f075df212a78f012bbe3e5a3293a98af74507 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 12:24:59 +0200 Subject: [PATCH 188/393] docs: add placeholder extension blueprints doc Signed-off-by: Patrik Oldsberg --- .../architecture/23-extension-blueprints.md | 11 +++++++++++ microsite/sidebars.json | 1 + 2 files changed, 12 insertions(+) create mode 100644 docs/frontend-system/architecture/23-extension-blueprints.md diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md new file mode 100644 index 0000000000..046834c9db --- /dev/null +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -0,0 +1,11 @@ +--- +id: extension-blueprints +title: Frontend Extension Blueprints +sidebar_label: Extensions Blueprints +# prettier-ignore +description: Frontend extensions +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +TODO diff --git a/microsite/sidebars.json b/microsite/sidebars.json index be17d76e03..de7f39f0c0 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -454,6 +454,7 @@ "frontend-system/architecture/app", "frontend-system/architecture/plugins", "frontend-system/architecture/extensions", + "frontend-system/architecture/extension-blueprints", "frontend-system/architecture/extension-overrides", "frontend-system/architecture/references", "frontend-system/architecture/utility-apis", From d33c7d78901a25f1d28eb51ff53b8a915ea84522 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 14:18:56 +0200 Subject: [PATCH 189/393] docs/frontend-system: initial extension blueprint docs with usage Signed-off-by: Patrik Oldsberg --- .../architecture/23-extension-blueprints.md | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md index 046834c9db..43212d4a87 100644 --- a/docs/frontend-system/architecture/23-extension-blueprints.md +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -8,4 +8,55 @@ description: Frontend extensions > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -TODO +The `createExtension` function and related APIs is considered a low-level building and fairly advanced building block, and is not typically what you would use when building plugins and features. Instead, the core APIs and plugins provide extension blueprints that makes it easier to create extensions for specific usages. These blueprints accept a number of parameters that is up to each blueprint to define, and then creates a new extension using the provided parameters. New blueprints are created using the `createExtensionBlueprint` function, and are by convention exported with the symbol `Blueprint`. If you are curious about what blueprints are available from a plugin or package, look for `*Blueprint` exports in the package's API, for plugins these are typically found in the `*-react` package. + +## Creating an extension from a blueprint + +Every extension blueprint provides a `make` method that can be used to create new extensions. It is a simple way to create a new extension where the base blueprint provides all the necessary functionality. All you need to do is to provide the necessary blueprint parameters, but you also have the ability to provide additional options, for example a `name` for the extension. + +The following is a simple example of how one might use the blueprint `make` method to create a new extension: + +```tsx +const myPageExtension = PageBlueprint.make({ + params: { + defaultPath: '/my-page', + loader: () => import('./components/MyPage').them(m => ), + }, +}); +``` + +The returned `myPageExtension` is an extension which is ready to be used in a plugin. It is the same type of object as is returned by the lower level `createExtension` function. + +## Creating an extension from a blueprint with overrides + +Every extension blueprint also provides a `makeWithOverrides` method. It is useful in cases where you want to provide additional integration points for an extension created with a blueprint. You might for example want to define additional inputs or configuration schema, or use the existing configuration to dynamically compute the parameters passed to the blueprint. + +The following is an example of how one might use the blueprint `makeWithOverrides` method to create a new extension: + +```tsx +const myPageExtension = PageBlueprint.makeWithOverrides({ + config: { + schema: { + layout: z => z.enum(['grid', 'rows']).default('grid'), + }, + }, + // The original blueprint factory is provided as the first argument + factory(originalFactory, { config }) { + // Call and forward the result from the original factory, providing + // the blueprint parameters as the first argument. + return originalFactory({ + defaultPath: '/my-page', + loader: () => + import('./components/MyPage').them(m => ( + // We can now access values from the factory context when providing + // the blueprint parameters, such as config values. + + )), + }); + }, +}); +``` + +When using `makeWithOverrides`, we no longer pass the blueprint parameters directly. Instead, we provide a `factory` function that receives the original blueprint factory as the first argument, and the extension factory context as the second. We can then call the original blueprint factory with the blueprint parameters and forward the result as the return value of out factory. Notice that when passing the blueprint parameters using this pattern we have access to a lot more information than when using the `make` method, at the cost of being more complex. + +Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. We therefore defer to the [extension overrides](./25-extension-overrides.md) documentation for more information on how to use the `makeWithOverrides` method. From 285e2b0b4bc138c3175162107bf1e3b5c1000941 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 14:19:18 +0200 Subject: [PATCH 190/393] docs/frontend-system: remove extension creator docs Signed-off-by: Patrik Oldsberg --- .../architecture/20-extensions.md | 36 +++---------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index eec9947c97..d1024d5e17 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -293,38 +293,6 @@ app: title: 'Backstage' ``` -## Extension Creators - -With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. - -If we follow the example from above all items of the navigation have similarities, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefore `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. - -```tsx -export const HomeNavIcon = createNavItemExtension({ - routeRef: routeRefForTheHomePage, - title: 'Home', - icon: HomeIcon, -}); -``` - -### Extension Kind - -With the example `HomeNavIcon` will end up on the extension input `items` of the extensions with the id `app/nav`. It raises the question what the `id` of the `HomeNavIcon` itself is. The extension creator for the navigation item has a defined `kind`, which by convention matches the own name. So in this example `createNavItemExtension` sets the kind to `nav-item`. - -The `id` of the extension is then build out of `namespace`, `name` & `kind` like the following - where `namespace` & `name` are optional properties that can be passed to the extension creator: - -``` -id: kind:namespace/name -``` - -For more information on naming of extension refer to the [naming patterns documentation](./50-naming-patterns.md). - -### Extension Creators in libraries - -Extension creators should be exported from frontend library packages (e.g. `*-react`) rather than plugin packages. - -If an extension is only for in-house tweaks, it's okay to put it in the plugin package. But if you want other open source plugins to use it, or you already have a `-react` package, always put extension creators in the `-react` package. - ## Extension Boundary The `ExtensionBoundary` wraps extensions with several React contexts for different purposes @@ -370,3 +338,7 @@ export function createSomeExtension< }); } ``` + +### Extension Creators (Deprecated) + +Previous iterations of the frontend system used an "extension creator" pattern, where `createExtension` was wrapped up in a function for creating specific extension kinds. This pattern was similar to [blueprints](./23-extension-blueprints.md), but much harder to implement and maintain. It has been deprecated in favor of the new blueprints API. For example, `createPageExtension` was the extension creator equivalent of `PageBlueprint`. From 24bed625443e991f6c9a1234434c9068f0b2e248 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 13 Aug 2024 16:03:41 +0200 Subject: [PATCH 191/393] backend-app-api: Remove deprecated service implementations Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-app-api/api-report.md | 207 -------------- packages/backend-app-api/src/http/index.ts | 6 +- .../auth/authServiceFactory.ts | 24 -- .../services/implementations/auth/index.ts | 17 -- .../cache/cacheServiceFactory.ts | 40 --- .../services/implementations/cache/index.ts | 17 -- .../services/implementations/config/index.ts | 18 -- .../config/rootConfigServiceFactory.ts | 61 ----- .../database/databaseServiceFactory.ts | 52 ---- .../implementations/database/index.ts | 17 -- .../services/implementations/deprecated.ts | 24 -- .../discovery/HostDiscovery.test.ts | 257 ------------------ .../discovery/HostDiscovery.ts | 71 ----- .../discovery/discoveryServiceFactory.ts | 35 --- .../implementations/discovery/index.ts | 18 -- .../httpAuth/httpAuthServiceFactory.ts | 24 -- .../implementations/httpAuth/index.ts | 17 -- .../httpRouter/createLifecycleMiddleware.ts | 45 --- .../httpRouter/httpRouterServiceFactory.ts | 30 -- .../implementations/httpRouter/index.ts | 19 -- .../src/services/implementations/index.ts | 10 - .../implementations/lifecycle/index.ts | 17 -- .../lifecycle/lifecycleServiceFactory.ts | 109 -------- .../services/implementations/logger/index.ts | 17 -- .../logger/loggerServiceFactory.ts | 30 -- .../implementations/permissions/index.ts | 17 -- .../permissions/permissionsServiceFactory.ts | 42 --- .../DefaultRootHttpRouter.test.ts | 124 --------- .../rootHttpRouter/DefaultRootHttpRouter.ts | 54 ---- .../implementations/rootHttpRouter/index.ts | 25 -- .../rootHttpRouterServiceFactory.ts | 46 ---- .../implementations/rootLifecycle/index.ts | 17 -- .../rootLifecycleServiceFactory.test.ts | 60 ---- .../rootLifecycleServiceFactory.ts | 124 --------- .../implementations/rootLogger/index.ts | 17 -- .../rootLogger/rootLoggerServiceFactory.ts | 30 -- .../implementations/scheduler/index.ts | 17 -- .../scheduler/schedulerServiceFactory.test.ts | 46 ---- .../scheduler/schedulerServiceFactory.ts | 41 --- .../implementations/urlReader/index.ts | 17 -- .../urlReader/urlReaderServiceFactory.ts | 39 --- .../implementations/userInfo/index.ts | 17 -- .../userInfo/userInfoServiceFactory.ts | 24 -- .../src/compat/legacy/legacy.test.ts | 2 +- .../package.json | 1 + .../src/manager/plugin-manager.test.ts | 6 +- .../src/next/wiring/TestBackend.ts | 10 +- .../src/service/router.credentials.test.ts | 6 +- yarn.lock | 1 + 49 files changed, 14 insertions(+), 1951 deletions(-) delete mode 100644 packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/auth/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/cache/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/config/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/database/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/deprecated.ts delete mode 100644 packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts delete mode 100644 packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts delete mode 100644 packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/discovery/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/httpAuth/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts delete mode 100644 packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/httpRouter/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/lifecycle/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/logger/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/permissions/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootLogger/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/scheduler/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts delete mode 100644 packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/urlReader/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts delete mode 100644 packages/backend-app-api/src/services/implementations/userInfo/index.ts delete mode 100644 packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index dea309ebb0..e4adcb56f2 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -6,52 +6,25 @@ /// import type { AppConfig } from '@backstage/config'; -import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CacheService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; import { CorsOptions } from 'cors'; -import { DatabaseService } from '@backstage/backend-plugin-api'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; import { ErrorRequestHandler } from 'express'; -import { Express as Express_2 } from 'express'; import { Format } from 'logform'; -import { Handler } from 'express'; import { HelmetOptions } from 'helmet'; import * as http from 'http'; -import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { HttpRouterService } from '@backstage/backend-plugin-api'; -import { HumanDuration } from '@backstage/types'; import { IdentityService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; -import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PermissionsService } from '@backstage/backend-plugin-api'; -import { RemoteConfigSourceOptions } from '@backstage/config-loader'; import { RequestHandler } from 'express'; -import { RequestListener } from 'http'; import { RootConfigService } from '@backstage/backend-plugin-api'; -import { RootHttpRouterService } from '@backstage/backend-plugin-api'; -import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { SchedulerService } from '@backstage/backend-plugin-api'; -import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; import { transport } from 'winston'; -import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { UserInfoService } from '@backstage/backend-plugin-api'; - -// @public @deprecated (undocumented) -export const authServiceFactory: ServiceFactoryCompat< - AuthService, - 'plugin', - 'singleton', - undefined ->; // @public (undocumented) export interface Backend { @@ -77,29 +50,11 @@ export interface Backend { stop(): Promise; } -// @public @deprecated (undocumented) -export const cacheServiceFactory: ServiceFactoryCompat< - CacheService, - 'plugin', - 'singleton', - undefined ->; - // Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) export const createConfigSecretEnumerator: typeof createConfigSecretEnumerator_2; -// Warning: (ae-forgotten-export) The symbol "createHttpServer_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const createHttpServer: typeof createHttpServer_2; - -// Warning: (ae-forgotten-export) The symbol "createLifecycleMiddleware_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export const createLifecycleMiddleware: typeof createLifecycleMiddleware_2; - // @public (undocumented) export function createSpecializedBackend( options: CreateSpecializedBackendOptions, @@ -111,72 +66,11 @@ export interface CreateSpecializedBackendOptions { defaultServiceFactories: ServiceFactory[]; } -// @public @deprecated (undocumented) -export const databaseServiceFactory: ServiceFactoryCompat< - DatabaseService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated -export class DefaultRootHttpRouter implements RootHttpRouterService { - // (undocumented) - static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter; - // (undocumented) - handler(): Handler; - // (undocumented) - use(path: string, handler: Handler): void; -} - -// Warning: (ae-forgotten-export) The symbol "DefaultRootHttpRouterOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export type DefaultRootHttpRouterOptions = DefaultRootHttpRouterOptions_2; - -// @public @deprecated (undocumented) -export const discoveryServiceFactory: ServiceFactoryCompat< - DiscoveryService, - 'plugin', - 'singleton', - undefined ->; - // Warning: (ae-forgotten-export) The symbol "ExtendedHttpServer_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) export type ExtendedHttpServer = ExtendedHttpServer_2; -// @public @deprecated -export class HostDiscovery implements DiscoveryService { - static fromConfig( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; - // (undocumented) - getBaseUrl(pluginId: string): Promise; - // (undocumented) - getExternalBaseUrl(pluginId: string): Promise; -} - -// @public @deprecated (undocumented) -export const httpAuthServiceFactory: ServiceFactoryCompat< - HttpAuthService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated -export const httpRouterServiceFactory: ServiceFactoryCompat< - HttpRouterService, - 'plugin', - 'singleton', - undefined ->; - // Warning: (ae-forgotten-export) The symbol "HttpServerCertificateOptions_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -201,19 +95,6 @@ export const identityServiceFactory: ServiceFactoryCompat< IdentityFactoryOptions >; -// Warning: (ae-forgotten-export) The symbol "LifecycleMiddlewareOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export type LifecycleMiddlewareOptions = LifecycleMiddlewareOptions_2; - -// @public @deprecated -export const lifecycleServiceFactory: ServiceFactoryCompat< - LifecycleService, - 'plugin', - 'singleton', - undefined ->; - // @public @deprecated export function loadBackendConfig(options: { remote?: LoadConfigOptionsRemote; @@ -224,14 +105,6 @@ export function loadBackendConfig(options: { config: Config; }>; -// @public @deprecated -export const loggerServiceFactory: ServiceFactoryCompat< - LoggerService, - 'plugin', - 'singleton', - undefined ->; - // @public @deprecated (undocumented) export class MiddlewareFactory { compression(): RequestHandler; @@ -253,14 +126,6 @@ export type MiddlewareFactoryErrorOptions = MiddlewareFactoryErrorOptions_2; // @public @deprecated (undocumented) export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2; -// @public @deprecated (undocumented) -export const permissionsServiceFactory: ServiceFactoryCompat< - PermissionsService, - 'plugin', - 'singleton', - undefined ->; - // Warning: (ae-forgotten-export) The symbol "readCorsOptions_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -276,62 +141,6 @@ export const readHelmetOptions: typeof readHelmetOptions_2; // @public @deprecated (undocumented) export const readHttpServerOptions: typeof readHttpServerOptions_2; -// @public @deprecated (undocumented) -export interface RootConfigFactoryOptions { - argv?: string[]; - remote?: Pick; - // (undocumented) - watch?: boolean; -} - -// @public @deprecated (undocumented) -export const rootConfigServiceFactory: ServiceFactoryCompat< - RootConfigService, - 'root', - 'singleton', - RootConfigFactoryOptions ->; - -// Warning: (ae-forgotten-export) The symbol "RootHttpRouterConfigureContext_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type RootHttpRouterConfigureContext = RootHttpRouterConfigureContext_2; - -// Warning: (ae-forgotten-export) The symbol "RootHttpRouterFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export type RootHttpRouterFactoryOptions = RootHttpRouterFactoryOptions_2; - -// @public @deprecated (undocumented) -export const rootHttpRouterServiceFactory: (( - options?: RootHttpRouterFactoryOptions_2 | undefined, -) => ServiceFactory) & - ServiceFactory; - -// @public @deprecated -export const rootLifecycleServiceFactory: ServiceFactoryCompat< - RootLifecycleService, - 'root', - 'singleton', - undefined ->; - -// @public @deprecated -export const rootLoggerServiceFactory: ServiceFactoryCompat< - RootLoggerService, - 'root', - 'singleton', - undefined ->; - -// @public @deprecated (undocumented) -export const schedulerServiceFactory: ServiceFactoryCompat< - SchedulerService, - 'plugin', - 'singleton', - undefined ->; - // @public @deprecated (undocumented) export const tokenManagerServiceFactory: ServiceFactoryCompat< TokenManagerService, @@ -340,22 +149,6 @@ export const tokenManagerServiceFactory: ServiceFactoryCompat< undefined >; -// @public @deprecated (undocumented) -export const urlReaderServiceFactory: ServiceFactoryCompat< - UrlReaderService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated (undocumented) -export const userInfoServiceFactory: ServiceFactoryCompat< - UserInfoService, - 'plugin', - 'singleton', - undefined ->; - // @public @deprecated export class WinstonLogger implements RootLoggerService { // (undocumented) diff --git a/packages/backend-app-api/src/http/index.ts b/packages/backend-app-api/src/http/index.ts index a056ed2d61..2ff62209f7 100644 --- a/packages/backend-app-api/src/http/index.ts +++ b/packages/backend-app-api/src/http/index.ts @@ -34,11 +34,7 @@ import { * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. */ export const readHttpServerOptions = _readHttpServerOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const createHttpServer = _createHttpServer; + /** * @public * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts deleted file mode 100644 index 988b907b4c..0000000000 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { authServiceFactory as _authServiceFactory } from '../../../../../backend-defaults/src/entrypoints/auth'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/auth` instead. - */ -export const authServiceFactory = _authServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/auth/index.ts b/packages/backend-app-api/src/services/implementations/auth/index.ts deleted file mode 100644 index 1b55d46a83..0000000000 --- a/packages/backend-app-api/src/services/implementations/auth/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { authServiceFactory } from './authServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts deleted file mode 100644 index 9b5e5ab16a..0000000000 --- a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CacheManager } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/cache` instead. - */ -export const cacheServiceFactory = createServiceFactory({ - service: coreServices.cache, - deps: { - config: coreServices.rootConfig, - logger: coreServices.rootLogger, - plugin: coreServices.pluginMetadata, - }, - async createRootContext({ config, logger }) { - return CacheManager.fromConfig(config, { logger }); - }, - async factory({ plugin }, manager) { - return manager.forPlugin(plugin.getId()).getClient(); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/cache/index.ts b/packages/backend-app-api/src/services/implementations/cache/index.ts deleted file mode 100644 index f96ee77182..0000000000 --- a/packages/backend-app-api/src/services/implementations/cache/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { cacheServiceFactory } from './cacheServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/config/index.ts b/packages/backend-app-api/src/services/implementations/config/index.ts deleted file mode 100644 index 1775ef2efc..0000000000 --- a/packages/backend-app-api/src/services/implementations/config/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { rootConfigServiceFactory } from './rootConfigServiceFactory'; -export type { RootConfigFactoryOptions } from './rootConfigServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts b/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts deleted file mode 100644 index c74474e629..0000000000 --- a/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { - ConfigSources, - RemoteConfigSourceOptions, -} from '@backstage/config-loader'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. - */ -export interface RootConfigFactoryOptions { - /** - * Process arguments to use instead of the default `process.argv()`. - */ - argv?: string[]; - - /** - * Enables and sets options for remote configuration loading. - */ - remote?: Pick; - watch?: boolean; -} - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. - */ -export const rootConfigServiceFactory = createServiceFactory( - (options?: RootConfigFactoryOptions) => ({ - service: coreServices.rootConfig, - deps: {}, - async factory() { - const source = ConfigSources.default({ - argv: options?.argv, - remote: options?.remote, - watch: options?.watch, - }); - console.log(`Loading config from ${source}`); - return await ConfigSources.toConfig(source); - }, - }), -); diff --git a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts deleted file mode 100644 index 972d8dd4ec..0000000000 --- a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DatabaseManager } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ConfigReader } from '@backstage/config'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/database` instead. - */ -export const databaseServiceFactory = createServiceFactory({ - service: coreServices.database, - deps: { - config: coreServices.rootConfig, - lifecycle: coreServices.lifecycle, - pluginMetadata: coreServices.pluginMetadata, - }, - async createRootContext({ config }) { - return config.getOptional('backend.database') - ? DatabaseManager.fromConfig(config) - : DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ); - }, - async factory({ pluginMetadata, lifecycle }, databaseManager) { - return databaseManager.forPlugin(pluginMetadata.getId(), { - pluginMetadata, - lifecycle, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/database/index.ts b/packages/backend-app-api/src/services/implementations/database/index.ts deleted file mode 100644 index d676c8013e..0000000000 --- a/packages/backend-app-api/src/services/implementations/database/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { databaseServiceFactory } from './databaseServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/deprecated.ts b/packages/backend-app-api/src/services/implementations/deprecated.ts deleted file mode 100644 index 347a968bce..0000000000 --- a/packages/backend-app-api/src/services/implementations/deprecated.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './auth'; -export * from './httpAuth'; -export * from './httpRouter'; -export * from './logger'; -export * from './rootHttpRouter'; -export * from './rootLogger'; -export * from './scheduler'; -export * from './userInfo'; diff --git a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts b/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts deleted file mode 100644 index 4e6aff5853..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConfigReader } from '@backstage/config'; -import { HostDiscovery } from './HostDiscovery'; - -describe('HostDiscovery', () => { - it('is created from config', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/api/catalog', - ); - }); - - it('strips trailing slashes in config', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40//', - listen: { port: 80, host: 'localhost' }, - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/api/catalog', - ); - }); - - it('can configure the base path', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - }), - { basePath: '/service' }, - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/service/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/service/catalog', - ); - }); - - it.each([ - [{ listen: ':80' }, 'http://localhost:80'], - [{ listen: ':40', https: true }, 'https://localhost:40'], - [{ listen: '127.0.0.1:80' }, 'http://127.0.0.1:80'], - [{ listen: '127.0.0.1:80', https: true }, 'https://127.0.0.1:80'], - [{ listen: '0.0.0.0:40' }, 'http://127.0.0.1:40'], - [{ listen: { port: 80 } }, 'http://localhost:80'], - [{ listen: { port: 8000 } }, 'http://localhost:8000'], - [{ listen: { port: 80, host: '0.0.0.0' } }, 'http://127.0.0.1:80'], - [{ listen: { port: 80, host: '::' } }, 'http://localhost:80'], - [{ listen: { port: 80, host: '::1' } }, 'http://[::1]:80'], - [{ listen: { port: 90, host: '::2' }, https: true }, 'https://[::2]:90'], - ])('resolves internal baseUrl for %j as %s', async (config, expected) => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - ...config, - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - `${expected}/api/catalog`, - ); - }); - - it('uses plugin specific targets from config if provided', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: { - internal: 'http://catalog-backend-internal:8080/api/catalog', - external: 'http://catalog-backend-external:8080/api/catalog', - }, - plugins: ['catalog'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend-internal:8080/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend-external:8080/api/catalog', - ); - }); - - it('uses a single target for internal and external for a plugin', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://catalog-backend:8080/api/catalog', - plugins: ['catalog'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend:8080/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend:8080/api/catalog', - ); - }); - - it('defaults to the backend baseUrl when there is not an endpoint for a plugin', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://catalog-backend:8080/api/catalog', - plugins: ['catalog'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( - 'http://localhost:80/api/scaffolder', - ); - await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( - 'http://localhost:40/api/scaffolder', - ); - }); - - it('replaces {{pluginId}} or {{ pluginId }} in the target', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://common-backend:8080/api/{{pluginId}}', - plugins: ['catalog', 'docs'], - }, - { - target: { - internal: 'http://scaffolder-internal:8080/api/{{ pluginId }}', - external: 'http://scaffolder-external:8080/api/{{ pluginId }}', - }, - plugins: ['scaffolder'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://common-backend:8080/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://common-backend:8080/api/catalog', - ); - await expect(discovery.getBaseUrl('docs')).resolves.toBe( - 'http://common-backend:8080/api/docs', - ); - await expect(discovery.getExternalBaseUrl('docs')).resolves.toBe( - 'http://common-backend:8080/api/docs', - ); - await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( - 'http://scaffolder-internal:8080/api/scaffolder', - ); - await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( - 'http://scaffolder-external:8080/api/scaffolder', - ); - }); - - it('encodes the pluginId', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://common-backend:8080/api/{{pluginId}}', - plugins: ['plugin/beta'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('plugin/beta')).resolves.toBe( - 'http://common-backend:8080/api/plugin%2Fbeta', - ); - await expect(discovery.getBaseUrl('plugin/alpha')).resolves.toBe( - 'http://localhost:80/api/plugin%2Falpha', - ); - await expect(discovery.getExternalBaseUrl('plugin/alpha')).resolves.toBe( - 'http://localhost:40/api/plugin%2Falpha', - ); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts b/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts deleted file mode 100644 index 5909dd1ae6..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Config } from '@backstage/config'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { HostDiscovery as _HostDiscovery } from '../../../../../backend-defaults/src/entrypoints/discovery'; - -/** - * HostDiscovery is a basic PluginEndpointDiscovery implementation - * that can handle plugins that are hosted in a single or multiple deployments. - * - * The deployment may be scaled horizontally, as long as the external URL - * is the same for all instances. However, internal URLs will always be - * resolved to the same host, so there won't be any balancing of internal traffic. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. - */ -export class HostDiscovery implements DiscoveryService { - /** - * Creates a new HostDiscovery discovery instance by reading - * from the `backend` config section, specifically the `.baseUrl` for - * discovering the external URL, and the `.listen` and `.https` config - * for the internal one. - * - * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`. - * eg. - * ```yaml - * discovery: - * endpoints: - * - target: https://internal.example.com/internal-catalog - * plugins: [catalog] - * - target: https://internal.example.com/secure/api/{{pluginId}} - * plugins: [auth, permission] - * - target: - * internal: https://internal.example.com/search - * external: https://example.com/search - * plugins: [search] - * ``` - * - * The basePath defaults to `/api`, meaning the default full internal - * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`. - */ - static fromConfig(config: Config, options?: { basePath?: string }) { - return new HostDiscovery(_HostDiscovery.fromConfig(config, options)); - } - - private constructor(private readonly impl: _HostDiscovery) {} - - async getBaseUrl(pluginId: string): Promise { - return this.impl.getBaseUrl(pluginId); - } - - async getExternalBaseUrl(pluginId: string): Promise { - return this.impl.getExternalBaseUrl(pluginId); - } -} diff --git a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts b/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts deleted file mode 100644 index b29a589438..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { HostDiscovery } from './HostDiscovery'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. - */ -export const discoveryServiceFactory = createServiceFactory({ - service: coreServices.discovery, - deps: { - config: coreServices.rootConfig, - }, - async factory({ config }) { - return HostDiscovery.fromConfig(config); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/discovery/index.ts b/packages/backend-app-api/src/services/implementations/discovery/index.ts deleted file mode 100644 index ee4851271a..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { discoveryServiceFactory } from './discoveryServiceFactory'; -export { HostDiscovery } from './HostDiscovery'; diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts deleted file mode 100644 index 373dd10ead..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { httpAuthServiceFactory as _httpAuthServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpAuth'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpAuth` instead. - */ -export const httpAuthServiceFactory = _httpAuthServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/index.ts b/packages/backend-app-api/src/services/implementations/httpAuth/index.ts deleted file mode 100644 index edd7e53026..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpAuth/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { httpAuthServiceFactory } from './httpAuthServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts b/packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts deleted file mode 100644 index d4864a23a6..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - createLifecycleMiddleware as _createLifecycleMiddleware, - type LifecycleMiddlewareOptions as _LifecycleMiddlewareOptions, -} from '../../../../../backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware'; - -/** - * Options for {@link createLifecycleMiddleware}. - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export type LifecycleMiddlewareOptions = _LifecycleMiddlewareOptions; - -/** - * Creates a middleware that pauses requests until the service has started. - * - * @remarks - * - * Requests that arrive before the service has started will be paused until startup is complete. - * If the service does not start within the provided timeout, the request will be rejected with a - * {@link @backstage/errors#ServiceUnavailableError}. - * - * If the service is shutting down, all requests will be rejected with a - * {@link @backstage/errors#ServiceUnavailableError}. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export const createLifecycleMiddleware = _createLifecycleMiddleware; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts deleted file mode 100644 index 0088e53f98..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { httpRouterServiceFactory as _httpRouterServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory'; - -/** - * HTTP route registration for plugins. - * - * See {@link @backstage/code-plugin-api#HttpRouterService} - * and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export const httpRouterServiceFactory = _httpRouterServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts b/packages/backend-app-api/src/services/implementations/httpRouter/index.ts deleted file mode 100644 index bc12480ae8..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { httpRouterServiceFactory } from './httpRouterServiceFactory'; -export { createLifecycleMiddleware } from './createLifecycleMiddleware'; -export type { LifecycleMiddlewareOptions } from './createLifecycleMiddleware'; diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts index 3a2fe819ed..5f9bb9479a 100644 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -14,15 +14,5 @@ * limitations under the License. */ -export * from './cache'; -export * from './config'; -export * from './database'; -export * from './discovery'; export * from './identity'; -export * from './lifecycle'; -export * from './permissions'; -export * from './rootLifecycle'; export * from './tokenManager'; -export * from './urlReader'; - -export * from './deprecated'; diff --git a/packages/backend-app-api/src/services/implementations/lifecycle/index.ts b/packages/backend-app-api/src/services/implementations/lifecycle/index.ts deleted file mode 100644 index 8dac4c26b4..0000000000 --- a/packages/backend-app-api/src/services/implementations/lifecycle/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { lifecycleServiceFactory } from './lifecycleServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts b/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts deleted file mode 100644 index b3b0135a7c..0000000000 --- a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - LifecycleService, - LifecycleServiceShutdownHook, - LifecycleServiceShutdownOptions, - LifecycleServiceStartupHook, - LifecycleServiceStartupOptions, - LoggerService, - PluginMetadataService, - RootLifecycleService, - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; - -/** - * @internal - * @deprecated - */ -export class BackendPluginLifecycleImpl implements LifecycleService { - constructor( - private readonly logger: LoggerService, - private readonly rootLifecycle: RootLifecycleService, - private readonly pluginMetadata: PluginMetadataService, - ) {} - - #hasStarted = false; - #startupTasks: Array<{ - hook: LifecycleServiceStartupHook; - options?: LifecycleServiceStartupOptions; - }> = []; - - addStartupHook( - hook: LifecycleServiceStartupHook, - options?: LifecycleServiceStartupOptions, - ): void { - if (this.#hasStarted) { - throw new Error('Attempted to add startup hook after startup'); - } - this.#startupTasks.push({ hook, options }); - } - - async startup(): Promise { - if (this.#hasStarted) { - return; - } - this.#hasStarted = true; - - this.logger.debug( - `Running ${this.#startupTasks.length} plugin startup tasks...`, - ); - await Promise.all( - this.#startupTasks.map(async ({ hook, options }) => { - const logger = options?.logger ?? this.logger; - try { - await hook(); - logger.debug(`Plugin startup hook succeeded`); - } catch (error) { - logger.error(`Plugin startup hook failed, ${error}`); - } - }), - ); - } - - addShutdownHook( - hook: LifecycleServiceShutdownHook, - options?: LifecycleServiceShutdownOptions, - ): void { - const plugin = this.pluginMetadata.getId(); - this.rootLifecycle.addShutdownHook(hook, { - logger: options?.logger?.child({ plugin }) ?? this.logger, - }); - } -} - -/** - * Allows plugins to register shutdown hooks that are run when the process is about to exit. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/lifecycle` instead. - */ -export const lifecycleServiceFactory = createServiceFactory({ - service: coreServices.lifecycle, - deps: { - logger: coreServices.logger, - rootLifecycle: coreServices.rootLifecycle, - pluginMetadata: coreServices.pluginMetadata, - }, - async factory({ rootLifecycle, logger, pluginMetadata }) { - return new BackendPluginLifecycleImpl( - logger, - rootLifecycle, - pluginMetadata, - ); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/logger/index.ts b/packages/backend-app-api/src/services/implementations/logger/index.ts deleted file mode 100644 index ca52bf5193..0000000000 --- a/packages/backend-app-api/src/services/implementations/logger/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { loggerServiceFactory } from './loggerServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts deleted file mode 100644 index 7e7b4b98b0..0000000000 --- a/packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { loggerServiceFactory as _loggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/logger/loggerServiceFactory'; - -/** - * Plugin-level logging. - * - * See {@link @backstage/code-plugin-api#LoggerService} - * and {@link https://backstage.io/docs/backend-system/core-services/logger | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/logger` instead. - */ -export const loggerServiceFactory = _loggerServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/permissions/index.ts b/packages/backend-app-api/src/services/implementations/permissions/index.ts deleted file mode 100644 index 781dda31a0..0000000000 --- a/packages/backend-app-api/src/services/implementations/permissions/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { permissionsServiceFactory } from './permissionsServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts deleted file mode 100644 index c8fa0e7bbf..0000000000 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/permissions` instead. - */ -export const permissionsServiceFactory = createServiceFactory({ - service: coreServices.permissions, - deps: { - auth: coreServices.auth, - config: coreServices.rootConfig, - discovery: coreServices.discovery, - tokenManager: coreServices.tokenManager, - }, - async factory({ auth, config, discovery, tokenManager }) { - return ServerPermissionClient.fromConfig(config, { - auth, - discovery, - tokenManager, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts deleted file mode 100644 index fc1de9b205..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import request from 'supertest'; -import { DefaultRootHttpRouter } from './DefaultRootHttpRouter'; - -describe('DefaultRootHttpRouter', () => { - it.each([ - [['/b'], '/a'], - [['/a'], '/aa/b'], - [['/aa'], '/a/b'], - [['/a/b'], '/aa'], - [['/b/a'], '/a'], - [['/a'], '/aa'], - ])(`with existing paths %s, adds %s without conflict`, (existing, added) => { - const router = DefaultRootHttpRouter.create(); - for (const path of existing) { - router.use(path, () => {}); - } - expect(() => router.use(added, () => {})).not.toThrow(); - }); - - it.each([ - [['/a'], '/a', '/a'], - [['/a'], '/a/b', '/a'], - [['/a/b'], '/a', '/a/b'], - ])( - `find conflict when existing paths %s, adds %s`, - (existing, added, conflict) => { - const router = DefaultRootHttpRouter.create(); - for (const path of existing) { - router.use(path, () => {}); - } - expect(() => router.use(added, () => {})).toThrow( - `Path ${added} conflicts with the existing path ${conflict}`, - ); - }, - ); - - it('should not be possible to supply an empty indexPath', () => { - expect(() => DefaultRootHttpRouter.create({ indexPath: '' })).toThrow( - 'indexPath option may not be an empty string', - ); - }); - - it('will always prioritize non-index paths', async () => { - const router = DefaultRootHttpRouter.create({ indexPath: '/x' }); - const app = express(); - app.use(router.handler()); - - const routerX = express.Router(); - routerX.get('/a', (_req, res) => res.status(201).end()); - - const routerY = express.Router(); - routerY.get('/a', (_req, res) => res.status(202).end()); - - await request(app).get('/').expect(404); - await request(app).get('/a').expect(404); - await request(app).get('/x/a').expect(404); - await request(app).get('/y/a').expect(404); - - router.use('/x', routerX); - - await request(app).get('/').expect(404); - await request(app).get('/a').expect(201); - await request(app).get('/x/a').expect(201); - await request(app).get('/y/a').expect(404); - - router.use('/y', routerY); - - await request(app).get('/').expect(404); - await request(app).get('/a').expect(201); - await request(app).get('/x/a').expect(201); - await request(app).get('/y/a').expect(202); - - expect('test').toBe('test'); - }); - - it('should treat unknown /api/ routes as 404', async () => { - const router = DefaultRootHttpRouter.create(); - const app = express(); - app.use(router.handler()); - - router.use('/api/app', (_req, res) => res.status(201).end()); - router.use('/api/catalog', (_req, res) => res.status(202).end()); - - await request(app).get('/').expect(201); - await request(app).get('/api/catalog').expect(202); - await request(app).get('/unknown').expect(201); - await request(app).get('/api/unknown').expect(404); - - expect('test').toBe('test'); - }); - - it('should treat unknown /api/ routes as 404 without an index path', async () => { - const router = DefaultRootHttpRouter.create({ indexPath: false }); - const app = express(); - app.use(router.handler()); - - router.use('/api/app', (_req, res) => res.status(201).end()); - router.use('/api/catalog', (_req, res) => res.status(202).end()); - - await request(app).get('/').expect(404); - await request(app).get('/api/catalog').expect(202); - await request(app).get('/unknown').expect(404); - await request(app).get('/api/unknown').expect(404); - - expect('test').toBe('test'); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts deleted file mode 100644 index 617d5055db..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { RootHttpRouterService } from '@backstage/backend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - DefaultRootHttpRouter as _DefaultRootHttpRouter, - DefaultRootHttpRouterOptions as _DefaultRootHttpRouterOptions, -} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/DefaultRootHttpRouter'; -import { Handler } from 'express'; - -/** - * Options for the {@link DefaultRootHttpRouter} class. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type DefaultRootHttpRouterOptions = _DefaultRootHttpRouterOptions; - -/** - * The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for - * {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export class DefaultRootHttpRouter implements RootHttpRouterService { - static create(options?: DefaultRootHttpRouterOptions) { - return new DefaultRootHttpRouter(_DefaultRootHttpRouter.create(options)); - } - - private constructor(private readonly impl: RootHttpRouterService) {} - - use(path: string, handler: Handler) { - this.impl.use(path, handler); - } - - handler(): Handler { - return (this.impl as any).handler(); - } -} diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts deleted file mode 100644 index 8437399fec..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { - rootHttpRouterServiceFactory, - type RootHttpRouterFactoryOptions, - type RootHttpRouterConfigureContext, -} from './rootHttpRouterServiceFactory'; -export { - DefaultRootHttpRouter, - type DefaultRootHttpRouterOptions, -} from './DefaultRootHttpRouter'; diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts deleted file mode 100644 index bef1913645..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - rootHttpRouterServiceFactory as _rootHttpRouterServiceFactory, - RootHttpRouterFactoryOptions as _RootHttpRouterFactoryOptions, - RootHttpRouterConfigureContext as _RootHttpRouterConfigureContext, -} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type RootHttpRouterConfigureContext = _RootHttpRouterConfigureContext; - -/** - * HTTP route registration for root services. - * - * See {@link @backstage/code-plugin-api#RootHttpRouterService} - * and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type RootHttpRouterFactoryOptions = _RootHttpRouterFactoryOptions; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const rootHttpRouterServiceFactory = _rootHttpRouterServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts deleted file mode 100644 index 86589cd23e..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { rootLifecycleServiceFactory } from './rootLifecycleServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts deleted file mode 100644 index 5992e38d50..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BackendLifecycleImpl } from './rootLifecycleServiceFactory'; -import { mockServices } from '@backstage/backend-test-utils'; - -describe('lifecycleService', () => { - it('should execute registered shutdown hook', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - const hook = jest.fn(); - service.addShutdownHook(() => hook()); - // should not execute the hook more than once. - await service.shutdown(); - await service.shutdown(); - await service.shutdown(); - expect(hook).toHaveBeenCalledTimes(1); - }); - - it('should not throw errors', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - service.addShutdownHook(() => { - throw new Error('oh no'); - }); - await expect(service.shutdown()).resolves.toBeUndefined(); - }); - - it('should not throw async errors', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - service.addShutdownHook(async () => { - throw new Error('oh no'); - }); - await expect(service.shutdown()).resolves.toBeUndefined(); - }); - - it('should reject hooks after trigger', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - await service.startup(); - expect(() => { - service.addStartupHook(() => {}); - }).toThrow('Attempted to add startup hook after startup'); - - await service.shutdown(); - expect(() => { - service.addShutdownHook(() => {}); - }).toThrow('Attempted to add shutdown hook after shutdown'); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts deleted file mode 100644 index bf5dc09b80..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceFactory, - coreServices, - LifecycleServiceStartupHook, - LifecycleServiceStartupOptions, - LifecycleServiceShutdownHook, - LifecycleServiceShutdownOptions, - RootLifecycleService, - LoggerService, -} from '@backstage/backend-plugin-api'; - -/** - * @internal - * @deprecated - */ -export class BackendLifecycleImpl implements RootLifecycleService { - constructor(private readonly logger: LoggerService) {} - - #hasStarted = false; - #startupTasks: Array<{ - hook: LifecycleServiceStartupHook; - options?: LifecycleServiceStartupOptions; - }> = []; - - addStartupHook( - hook: LifecycleServiceStartupHook, - options?: LifecycleServiceStartupOptions, - ): void { - if (this.#hasStarted) { - throw new Error('Attempted to add startup hook after startup'); - } - this.#startupTasks.push({ hook, options }); - } - - async startup(): Promise { - if (this.#hasStarted) { - return; - } - this.#hasStarted = true; - - this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`); - await Promise.all( - this.#startupTasks.map(async ({ hook, options }) => { - const logger = options?.logger ?? this.logger; - try { - await hook(); - logger.debug(`Startup hook succeeded`); - } catch (error) { - logger.error(`Startup hook failed, ${error}`); - } - }), - ); - } - - #hasShutdown = false; - #shutdownTasks: Array<{ - hook: LifecycleServiceShutdownHook; - options?: LifecycleServiceShutdownOptions; - }> = []; - - addShutdownHook( - hook: LifecycleServiceShutdownHook, - options?: LifecycleServiceShutdownOptions, - ): void { - if (this.#hasShutdown) { - throw new Error('Attempted to add shutdown hook after shutdown'); - } - this.#shutdownTasks.push({ hook, options }); - } - - async shutdown(): Promise { - if (this.#hasShutdown) { - return; - } - this.#hasShutdown = true; - - this.logger.debug( - `Running ${this.#shutdownTasks.length} shutdown tasks...`, - ); - await Promise.all( - this.#shutdownTasks.map(async ({ hook, options }) => { - const logger = options?.logger ?? this.logger; - try { - await hook(); - logger.debug(`Shutdown hook succeeded`); - } catch (error) { - logger.error(`Shutdown hook failed, ${error}`); - } - }), - ); - } -} - -/** - * Allows plugins to register shutdown hooks that are run when the process is about to exit. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLifecycle` instead. - */ -export const rootLifecycleServiceFactory = createServiceFactory({ - service: coreServices.rootLifecycle, - deps: { - logger: coreServices.rootLogger, - }, - async factory({ logger }) { - return new BackendLifecycleImpl(logger); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/rootLogger/index.ts b/packages/backend-app-api/src/services/implementations/rootLogger/index.ts deleted file mode 100644 index 366cd53163..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLogger/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { rootLoggerServiceFactory } from './rootLoggerServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts deleted file mode 100644 index af1931c438..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { rootLoggerServiceFactory as _rootLoggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/rootLogger/rootLoggerServiceFactory'; - -/** - * Root-level logging. - * - * See {@link @backstage/code-plugin-api#RootLoggerService} - * and {@link https://backstage.io/docs/backend-system/core-services/root-logger | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead. - */ -export const rootLoggerServiceFactory = _rootLoggerServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/scheduler/index.ts b/packages/backend-app-api/src/services/implementations/scheduler/index.ts deleted file mode 100644 index 8b67006a4d..0000000000 --- a/packages/backend-app-api/src/services/implementations/scheduler/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { schedulerServiceFactory } from './schedulerServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts deleted file mode 100644 index b16509cf96..0000000000 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { coreServices } from '@backstage/backend-plugin-api'; -import { ServiceFactoryTester } from '@backstage/backend-test-utils'; -import { schedulerServiceFactory } from './schedulerServiceFactory'; - -describe('schedulerFactory', () => { - it('creates sidecar database features', async () => { - const tester = ServiceFactoryTester.from(schedulerServiceFactory()); - - const scheduler = await tester.getSubject(); - await scheduler.scheduleTask({ - id: 'task1', - timeout: { seconds: 1 }, - frequency: { seconds: 1 }, - fn: async () => {}, - }); - - const database = await tester.getService(coreServices.database); - - const client = await database.getClient(); - await expect( - client.from('backstage_backend_tasks__tasks').count(), - ).resolves.toEqual([{ 'count(*)': 1 }]); - await expect( - client.from('backstage_backend_tasks__knex_migrations').count(), - ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]); - await expect( - client.from('backstage_backend_tasks__knex_migrations_lock').count(), - ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts deleted file mode 100644 index b4370761d9..0000000000 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { TaskScheduler } from '@backstage/backend-tasks'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/scheduler` instead. - */ -export const schedulerServiceFactory = createServiceFactory({ - service: coreServices.scheduler, - deps: { - plugin: coreServices.pluginMetadata, - databaseManager: coreServices.database, - logger: coreServices.logger, - }, - async factory({ plugin, databaseManager, logger }) { - return TaskScheduler.forPlugin({ - pluginId: plugin.getId(), - databaseManager, - logger, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/urlReader/index.ts b/packages/backend-app-api/src/services/implementations/urlReader/index.ts deleted file mode 100644 index 6a4b9f65be..0000000000 --- a/packages/backend-app-api/src/services/implementations/urlReader/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { urlReaderServiceFactory } from './urlReaderServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts b/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts deleted file mode 100644 index 44caf25ad6..0000000000 --- a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { UrlReaders } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/urlReader` instead. - */ -export const urlReaderServiceFactory = createServiceFactory({ - service: coreServices.urlReader, - deps: { - config: coreServices.rootConfig, - logger: coreServices.logger, - }, - async factory({ config, logger }) { - return UrlReaders.default({ - config, - logger, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/userInfo/index.ts b/packages/backend-app-api/src/services/implementations/userInfo/index.ts deleted file mode 100644 index 13b42f053c..0000000000 --- a/packages/backend-app-api/src/services/implementations/userInfo/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { userInfoServiceFactory } from './userInfoServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts b/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts deleted file mode 100644 index 746f480c41..0000000000 --- a/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { userInfoServiceFactory as _userInfoServiceFactory } from '../../../../../backend-defaults/src/entrypoints/userInfo'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/userInfo` instead. - */ -export const userInfoServiceFactory = _userInfoServiceFactory; diff --git a/packages/backend-common/src/compat/legacy/legacy.test.ts b/packages/backend-common/src/compat/legacy/legacy.test.ts index e5d395a8f9..08a645f660 100644 --- a/packages/backend-common/src/compat/legacy/legacy.test.ts +++ b/packages/backend-common/src/compat/legacy/legacy.test.ts @@ -24,7 +24,7 @@ import { Router } from 'express'; import { createLegacyAuthAdapters } from '..'; import { legacyPlugin } from './legacy'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { authServiceFactory } from '../../../../backend-app-api/src/services/implementations/auth'; +import { authServiceFactory } from '../../../../backend-defaults/src/entrypoints/auth'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { tokenManagerServiceFactory } from '../../../../backend-app-api/src/services/implementations/tokenManager'; diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 4fdb93ee3a..f4a3237762 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -70,6 +70,7 @@ "winston": "^3.2.1" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 8531624509..bd5bf1168a 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -37,15 +37,13 @@ import { import { ScannedPluginManifest, ScannedPluginPackage } from '../scanner/types'; import { randomUUID } from 'crypto'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { - createSpecializedBackend, - rootLifecycleServiceFactory, -} from '@backstage/backend-app-api'; +import { createSpecializedBackend } from '@backstage/backend-app-api'; import { ConfigSources } from '@backstage/config-loader'; import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils'; import { PluginScanner } from '../scanner/plugin-scanner'; import { findPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; +import { rootLifecycleServiceFactory } from '@backstage/backend-defaults'; describe('backend-dynamic-feature-service', () => { const mockDir = createMockDirectory(); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index cf98c25e4a..09ecfab955 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -18,10 +18,7 @@ import { Backend, createSpecializedBackend, MiddlewareFactory, - createHttpServer, ExtendedHttpServer, - HostDiscovery, - DefaultRootHttpRouter, } from '@backstage/backend-app-api'; import { createServiceFactory, @@ -40,7 +37,12 @@ import { InternalBackendFeature, InternalBackendRegistrations, } from '@backstage/backend-plugin-api/src/wiring/types'; -import { createHealthRouter } from '@backstage/backend-defaults/rootHttpRouter'; +import { + DefaultRootHttpRouter, + createHealthRouter, + createHttpServer, +} from '@backstage/backend-defaults/rootHttpRouter'; +import { HostDiscovery } from '@backstage/backend-defaults/discovery'; /** @public */ export interface TestBackendOptions { diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index ac32179ef3..a363e7c632 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -import { - authServiceFactory, - httpAuthServiceFactory, -} from '@backstage/backend-app-api'; +import { authServiceFactory } from '@backstage/backend-defaults/auth'; +import { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth'; import { mockServices, registerMswTestHooks, diff --git a/yarn.lock b/yarn.lock index 9c292f5903..4a13ea1184 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3715,6 +3715,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From d45026fbf1b8b7ac02029293b500558842fcaba0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 13 Aug 2024 16:32:51 +0200 Subject: [PATCH 192/393] fix(backend-dynamic-feature-service): fix root lifecycle service factory import Signed-off-by: Camila Belo Signed-off-by: Johan Haals --- .../src/manager/plugin-manager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index bd5bf1168a..9c45cc53f6 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -43,7 +43,7 @@ import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils'; import { PluginScanner } from '../scanner/plugin-scanner'; import { findPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; -import { rootLifecycleServiceFactory } from '@backstage/backend-defaults'; +import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle'; describe('backend-dynamic-feature-service', () => { const mockDir = createMockDirectory(); From 0ad13df777123b852078466b9fcffcc43cda33a0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 13 Aug 2024 16:34:02 +0200 Subject: [PATCH 193/393] refactor: remove deprecated url reader service types Signed-off-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-common/api-report.md | 36 ----------- .../backend-common/src/deprecated/index.ts | 63 ------------------- packages/backend-plugin-api/api-report.md | 28 --------- .../services/definitions/UrlReaderService.ts | 47 -------------- .../src/services/definitions/index.ts | 9 --- plugins/search-backend-node/api-report.md | 4 +- plugins/search-backend-node/package.json | 3 +- ...ewlineDelimitedJsonCollatorFactory.test.ts | 14 +++-- .../NewlineDelimitedJsonCollatorFactory.ts | 7 +-- plugins/techdocs-node/src/helpers.test.ts | 24 +++---- yarn.lock | 2 +- 11 files changed, 27 insertions(+), 210 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 23ed13b62c..e63ff83318 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -48,21 +48,12 @@ import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; import { ReadCommitResult } from 'isomorphic-git'; -import { ReadTreeOptions as ReadTreeOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponse as ReadTreeResponse_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponseDirOptions as ReadTreeResponseDirOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponseFile as ReadTreeResponseFile_2 } from '@backstage/backend-plugin-api'; -import { ReadUrlOptions as ReadUrlOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadUrlResponse as ReadUrlResponse_2 } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { SchedulerService } from '@backstage/backend-plugin-api'; -import { SearchOptions as SearchOptions_2 } from '@backstage/backend-plugin-api'; -import { SearchResponse as SearchResponse_2 } from '@backstage/backend-plugin-api'; -import { SearchResponseFile as SearchResponseFile_2 } from '@backstage/backend-plugin-api'; import { Server } from 'http'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; @@ -505,15 +496,6 @@ export interface PullOptions { // @public @deprecated (undocumented) export type ReaderFactory = ReaderFactory_2; -// @public @deprecated (undocumented) -export type ReadTreeOptions = ReadTreeOptions_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponse = ReadTreeResponse_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponseDirOptions = ReadTreeResponseDirOptions_2; - // Warning: (ae-forgotten-export) The symbol "ReadTreeResponseFactory_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -524,15 +506,6 @@ export type ReadTreeResponseFactory = ReadTreeResponseFactory_2; // @public @deprecated (undocumented) export type ReadTreeResponseFactoryOptions = ReadTreeResponseFactoryOptions_2; -// @public @deprecated (undocumented) -export type ReadTreeResponseFile = ReadTreeResponseFile_2; - -// @public @deprecated (undocumented) -export type ReadUrlOptions = ReadUrlOptions_2; - -// @public @deprecated (undocumented) -export type ReadUrlResponse = ReadUrlResponse_2; - // Warning: (ae-forgotten-export) The symbol "ReadUrlResponseFactory_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -577,15 +550,6 @@ export type RunContainerOptions = { pullOptions?: PullOptions; }; -// @public @deprecated (undocumented) -export type SearchOptions = SearchOptions_2; - -// @public @deprecated (undocumented) -export type SearchResponse = SearchResponse_2; - -// @public @deprecated (undocumented) -export type SearchResponseFile = SearchResponseFile_2; - // @public @deprecated export class ServerTokenManager implements TokenManager { // (undocumented) diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index e3e4101d25..8c37017eb7 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -85,15 +85,6 @@ import { resolvePackagePath as _resolvePackagePath, resolveSafeChildPath as _resolveSafeChildPath, isChildPath as _isChildPath, - ReadTreeOptions as _ReadTreeOptions, - ReadTreeResponse as _ReadTreeResponse, - ReadTreeResponseFile as _ReadTreeResponseFile, - ReadTreeResponseDirOptions as _ReadTreeResponseDirOptions, - ReadUrlOptions as _ReadUrlOptions, - ReadUrlResponse as _ReadUrlResponse, - SearchOptions as _SearchOptions, - SearchResponse as _SearchResponse, - SearchResponseFile as _SearchResponseFile, UrlReaderService as _UrlReaderService, LifecycleService, PluginMetadataService, @@ -412,60 +403,6 @@ export type ReadUrlResponseFactoryFromStreamOptions = */ export type UrlReaderPredicateTuple = _UrlReaderPredicateTuple; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeOptions = _ReadTreeOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponse = _ReadTreeResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponseFile = _ReadTreeResponseFile; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponseDirOptions = _ReadTreeResponseDirOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadUrlOptions = _ReadUrlOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead - */ -export type ReadUrlResponse = _ReadUrlResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead - */ -export type SearchOptions = _SearchOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead - */ -export type SearchResponse = _SearchResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead - */ -export type SearchResponseFile = _SearchResponseFile; - /** * @public * @deprecated Use `UrlReaderService` from `@backstage/backend-plugin-api` instead diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 319ddf0bc6..0864064625 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -553,25 +553,6 @@ export function readSchedulerServiceTaskScheduleDefinitionFromConfig( config: Config, ): SchedulerServiceTaskScheduleDefinition; -// @public @deprecated (undocumented) -export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; - -// @public @deprecated (undocumented) -export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; - -// @public @deprecated (undocumented) -export type ReadTreeResponseDirOptions = - UrlReaderServiceReadTreeResponseDirOptions; - -// @public @deprecated (undocumented) -export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; - -// @public @deprecated (undocumented) -export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; - -// @public @deprecated (undocumented) -export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; - // @public export function resolvePackagePath(name: string, ...paths: string[]): string; @@ -707,15 +688,6 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig { timeout: string | HumanDuration; } -// @public @deprecated (undocumented) -export type SearchOptions = UrlReaderServiceSearchOptions; - -// @public @deprecated (undocumented) -export type SearchResponse = UrlReaderServiceSearchResponse; - -// @public @deprecated (undocumented) -export type SearchResponseFile = UrlReaderServiceSearchResponseFile; - // @public (undocumented) export interface ServiceFactory< TService = unknown, diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index 25b787107e..9b327cfbf0 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -368,50 +368,3 @@ export type UrlReaderServiceSearchResponseFile = { */ lastModifiedAt?: Date; }; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeOptions` instead - */ -export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponse` instead - */ -export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` instead - */ -export type ReadTreeResponseDirOptions = - UrlReaderServiceReadTreeResponseDirOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseFile` instead - */ -export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlResponse` instead - */ -export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlOptions` instead - */ -export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchOptions` instead - */ -export type SearchOptions = UrlReaderServiceSearchOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponse` instead - */ -export type SearchResponse = UrlReaderServiceSearchResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponseFile` instead - */ -export type SearchResponseFile = UrlReaderServiceSearchResponseFile; diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 5e30d444fd..194658dc0e 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -66,15 +66,6 @@ export type { } from './SchedulerService'; export type { TokenManagerService } from './TokenManagerService'; export type { - ReadTreeOptions, - ReadTreeResponse, - ReadTreeResponseDirOptions, - ReadTreeResponseFile, - ReadUrlResponse, - ReadUrlOptions, - SearchOptions, - SearchResponse, - SearchResponseFile, UrlReaderServiceReadTreeOptions, UrlReaderServiceReadTreeResponse, UrlReaderServiceReadTreeResponseDirOptions, diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index 5e8961a65e..03269c72e8 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -20,7 +20,7 @@ import { SearchQuery } from '@backstage/plugin-search-common'; import { TaskFunction } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; import { Transform } from 'stream'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Writable } from 'stream'; // @public @@ -138,7 +138,7 @@ export class NewlineDelimitedJsonCollatorFactory export type NewlineDelimitedJsonCollatorFactoryOptions = { type: string; searchPattern: string; - reader: UrlReader; + reader: UrlReaderService; logger: LoggerService; visibilityPermission?: Permission; }; diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index d7a7191faa..c8a833e92a 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -53,7 +53,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", @@ -67,7 +67,6 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/ndjson": "^2.0.1" diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts index 45cc98e9ba..45f3635232 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { - ReadUrlResponse, - UrlReader, - UrlReaders, -} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Readable } from 'stream'; import { NewlineDelimitedJsonCollatorFactory } from './NewlineDelimitedJsonCollatorFactory'; import { TestPipeline } from '../test-utils'; import { mockServices } from '@backstage/backend-test-utils'; +import { + UrlReaderService, + UrlReaderServiceReadUrlResponse, +} from '@backstage/backend-plugin-api'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; describe('DefaultCatalogCollatorFactory', () => { const config = new ConfigReader({}); @@ -42,7 +42,9 @@ describe('DefaultCatalogCollatorFactory', () => { describe('getCollator', () => { let readable: Readable; let reader: jest.Mocked< - UrlReader & { readUrl: jest.Mock> } + UrlReaderService & { + readUrl: jest.Mock>; + } >; let factory: NewlineDelimitedJsonCollatorFactory; diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index e7d749dd41..bf78b98040 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -18,9 +18,8 @@ import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Permission } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; -import { UrlReader } from '@backstage/backend-common'; import { parse as parseNdjson } from 'ndjson'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; /** * Options for instantiate NewlineDelimitedJsonCollatorFactory @@ -29,7 +28,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; export type NewlineDelimitedJsonCollatorFactoryOptions = { type: string; searchPattern: string; - reader: UrlReader; + reader: UrlReaderService; logger: LoggerService; visibilityPermission?: Permission; }; @@ -73,7 +72,7 @@ export class NewlineDelimitedJsonCollatorFactory private constructor( type: string, private readonly searchPattern: string, - private readonly reader: UrlReader, + private readonly reader: UrlReaderService, private readonly logger: LoggerService, visibilityPermission: Permission | undefined, ) { diff --git a/plugins/techdocs-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index 4f77a5432b..e0c9ac41ca 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -14,13 +14,6 @@ * limitations under the License. */ -import { - ReadTreeResponse, - ReadUrlOptions, - ReadUrlResponse, - SearchResponse, - UrlReader, -} from '@backstage/backend-common'; import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -34,6 +27,13 @@ import { parseReferenceAnnotation, transformDirLocation, } from './helpers'; +import { + UrlReaderService, + UrlReaderServiceReadTreeResponse, + UrlReaderServiceReadUrlOptions, + UrlReaderServiceReadUrlResponse, + UrlReaderServiceSearchResponse, +} from '@backstage/backend-plugin-api'; jest.mock('@backstage/catalog-model', () => ({ ...jest.requireActual('@backstage/catalog-model'), @@ -290,19 +290,19 @@ describe('getLocationForEntity', () => { describe('getDocFilesFromRepository', () => { it('should read a remote directory using UrlReader.readTree', async () => { - class MockUrlReader implements UrlReader { + class MockUrlReader implements UrlReaderService { async read() { return Buffer.from('mock'); } async readUrl( _url: string, - _options?: ReadUrlOptions | undefined, - ): Promise { + _options?: UrlReaderServiceReadUrlOptions | undefined, + ): Promise { throw new Error('Method not implemented.'); } - async readTree(): Promise { + async readTree(): Promise { return { dir: async () => { return '/tmp/testfolder'; @@ -317,7 +317,7 @@ describe('getDocFilesFromRepository', () => { }; } - async search(): Promise { + async search(): Promise { return { etag: '', files: [], diff --git a/yarn.lock b/yarn.lock index 4a13ea1184..b6cfe624cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7553,7 +7553,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-search-backend-node@workspace:plugins/search-backend-node" dependencies: - "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From 29ee082ed6bfe1298778675a7d221dc515512e2f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 09:36:59 +0200 Subject: [PATCH 194/393] backend-app-api: Remove deprecated MiddlewareFactory Signed-off-by: Johan Haals --- packages/backend-app-api/api-report.md | 61 +----- packages/backend-app-api/src/http/index.ts | 183 ------------------ packages/backend-app-api/src/index.ts | 1 - packages/backend-test-utils/api-report.md | 2 +- .../src/next/wiring/TestBackend.ts | 9 +- plugins/kubernetes-backend/package.json | 1 + .../src/routes/resourceRoutes.test.ts | 2 +- .../src/service/KubernetesBuilder.test.ts | 2 +- plugins/kubernetes-node/package.json | 1 + .../src/auth/PinnipedHelper.test.ts | 2 +- plugins/scaffolder-backend/package.json | 1 + .../src/service/router.test.ts | 2 +- yarn.lock | 3 + 13 files changed, 15 insertions(+), 255 deletions(-) delete mode 100644 packages/backend-app-api/src/http/index.ts diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index e4adcb56f2..ba2a8734b0 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -3,23 +3,15 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - import type { AppConfig } from '@backstage/config'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; -import { CorsOptions } from 'cors'; -import { ErrorRequestHandler } from 'express'; import { Format } from 'logform'; -import { HelmetOptions } from 'helmet'; -import * as http from 'http'; import { IdentityService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { RequestHandler } from 'express'; -import { RootConfigService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; @@ -66,21 +58,6 @@ export interface CreateSpecializedBackendOptions { defaultServiceFactories: ServiceFactory[]; } -// Warning: (ae-forgotten-export) The symbol "ExtendedHttpServer_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ExtendedHttpServer = ExtendedHttpServer_2; - -// Warning: (ae-forgotten-export) The symbol "HttpServerCertificateOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type HttpServerCertificateOptions = HttpServerCertificateOptions_2; - -// Warning: (ae-forgotten-export) The symbol "HttpServerOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type HttpServerOptions = HttpServerOptions_2; - // @public @deprecated export type IdentityFactoryOptions = { issuer?: string; @@ -105,42 +82,6 @@ export function loadBackendConfig(options: { config: Config; }>; -// @public @deprecated (undocumented) -export class MiddlewareFactory { - compression(): RequestHandler; - cors(): RequestHandler; - static create(options: MiddlewareFactoryOptions): MiddlewareFactory; - error(options?: MiddlewareFactoryErrorOptions): ErrorRequestHandler; - helmet(): RequestHandler; - logging(): RequestHandler; - notFound(): RequestHandler; -} - -// Warning: (ae-forgotten-export) The symbol "MiddlewareFactoryErrorOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type MiddlewareFactoryErrorOptions = MiddlewareFactoryErrorOptions_2; - -// Warning: (ae-forgotten-export) The symbol "MiddlewareFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2; - -// Warning: (ae-forgotten-export) The symbol "readCorsOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const readCorsOptions: typeof readCorsOptions_2; - -// Warning: (ae-forgotten-export) The symbol "readHelmetOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const readHelmetOptions: typeof readHelmetOptions_2; - -// Warning: (ae-forgotten-export) The symbol "readHttpServerOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const readHttpServerOptions: typeof readHttpServerOptions_2; - // @public @deprecated (undocumented) export const tokenManagerServiceFactory: ServiceFactoryCompat< TokenManagerService, diff --git a/packages/backend-app-api/src/http/index.ts b/packages/backend-app-api/src/http/index.ts deleted file mode 100644 index 2ff62209f7..0000000000 --- a/packages/backend-app-api/src/http/index.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorRequestHandler, RequestHandler } from 'express'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - readHttpServerOptions as _readHttpServerOptions, - createHttpServer as _createHttpServer, - MiddlewareFactory as _MiddlewareFactory, - readCorsOptions as _readCorsOptions, - readHelmetOptions as _readHelmetOptions, - type MiddlewareFactoryErrorOptions as _MiddlewareFactoryErrorOptions, - type MiddlewareFactoryOptions as _MiddlewareFactoryOptions, - type ExtendedHttpServer as _ExtendedHttpServer, - type HttpServerCertificateOptions as _HttpServerCertificateOptions, - type HttpServerOptions as _HttpServerOptions, -} from '../../../backend-defaults/src/entrypoints/rootHttpRouter/http'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const readHttpServerOptions = _readHttpServerOptions; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const readCorsOptions = _readCorsOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const readHelmetOptions = _readHelmetOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export class MiddlewareFactory { - /** - * Creates a new {@link MiddlewareFactory}. - */ - static create(options: MiddlewareFactoryOptions) { - return new MiddlewareFactory(_MiddlewareFactory.create(options)); - } - - private constructor(private readonly impl: _MiddlewareFactory) {} - - /** - * Returns a middleware that unconditionally produces a 404 error response. - * - * @remarks - * - * Typically you want to place this middleware at the end of the chain, such - * that it's the last one attempted after no other routes matched. - * - * @returns An Express request handler - */ - notFound(): RequestHandler { - return this.impl.notFound(); - } - - /** - * Returns the compression middleware. - * - * @remarks - * - * The middleware will attempt to compress response bodies for all requests - * that traverse through the middleware. - */ - compression(): RequestHandler { - return this.impl.compression(); - } - - /** - * Returns a request logging middleware. - * - * @remarks - * - * Typically you want to place this middleware at the start of the chain, such - * that it always logs requests whether they are "caught" by handlers farther - * down or not. - * - * @returns An Express request handler - */ - logging(): RequestHandler { - return this.impl.logging(); - } - - /** - * Returns a middleware that implements the helmet library. - * - * @remarks - * - * This middleware applies security policies to incoming requests and outgoing - * responses. It is configured using config keys such as `backend.csp`. - * - * @see {@link https://helmetjs.github.io/} - * - * @returns An Express request handler - */ - helmet(): RequestHandler { - return this.impl.helmet(); - } - - /** - * Returns a middleware that implements the cors library. - * - * @remarks - * - * This middleware handles CORS. It is configured using the config key - * `backend.cors`. - * - * @see {@link https://github.com/expressjs/cors} - * - * @returns An Express request handler - */ - cors(): RequestHandler { - return this.impl.cors(); - } - - /** - * Express middleware to handle errors during request processing. - * - * @remarks - * - * This is commonly the very last middleware in the chain. - * - * Its primary purpose is not to do translation of business logic exceptions, - * but rather to be a global catch-all for uncaught "fatal" errors that are - * expected to result in a 500 error. However, it also does handle some common - * error types (such as http-error exceptions, and the well-known error types - * in the `@backstage/errors` package) and returns the enclosed status code - * accordingly. - * - * It will also produce a response body with a serialized form of the error, - * unless a previous handler already did send a body. See - * {@link @backstage/errors#ErrorResponseBody} for the response shape used. - * - * @returns An Express error request handler - */ - error(options: MiddlewareFactoryErrorOptions = {}): ErrorRequestHandler { - return this.impl.error(options); - } -} -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type MiddlewareFactoryErrorOptions = _MiddlewareFactoryErrorOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type MiddlewareFactoryOptions = _MiddlewareFactoryOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type ExtendedHttpServer = _ExtendedHttpServer; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type HttpServerCertificateOptions = _HttpServerCertificateOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type HttpServerOptions = _HttpServerOptions; diff --git a/packages/backend-app-api/src/index.ts b/packages/backend-app-api/src/index.ts index 13864a3470..9fead85fe8 100644 --- a/packages/backend-app-api/src/index.ts +++ b/packages/backend-app-api/src/index.ts @@ -21,7 +21,6 @@ */ export * from './config'; -export * from './http'; export * from './logging'; export * from './wiring'; export * from './services/implementations'; diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 584fedb1f5..4937a7810c 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -19,7 +19,7 @@ import { CacheService } from '@backstage/backend-plugin-api'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { EventsService } from '@backstage/plugin-events-node'; -import { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 09ecfab955..6b4110d4de 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Backend, - createSpecializedBackend, - MiddlewareFactory, - ExtendedHttpServer, -} from '@backstage/backend-app-api'; +import { Backend, createSpecializedBackend } from '@backstage/backend-app-api'; import { createServiceFactory, BackendFeature, @@ -39,6 +34,8 @@ import { } from '@backstage/backend-plugin-api/src/wiring/types'; import { DefaultRootHttpRouter, + ExtendedHttpServer, + MiddlewareFactory, createHealthRouter, createHttpServer, } from '@backstage/backend-defaults/rootHttpRouter'; diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index c5c6dc05eb..0b1271636b 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -99,6 +99,7 @@ }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-permission-backend": "workspace:^", diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index cfa89a6bfd..d041fdc40b 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -20,7 +20,7 @@ import { mockServices, startTestBackend, } from '@backstage/backend-test-utils'; -import { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { ExtendedHttpServer } from '@backstage/backend-defaults'; import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; import { createBackendModule } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 5edb8845d4..60683a74f3 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -56,7 +56,7 @@ import { kubernetesFetcherExtensionPoint, kubernetesServiceLocatorExtensionPoint, } from '@backstage/plugin-kubernetes-node'; -import { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; describe('API integration tests', () => { let app: ExtendedHttpServer; diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 92ece79c6d..4873e277c1 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -49,6 +49,7 @@ "devDependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts index 24cd459fde..65e48affc0 100644 --- a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ClusterDetails } from '../types'; import { mockServices, @@ -37,6 +36,7 @@ import { JsonObject } from '@backstage/types'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { loggerToWinstonLogger } from '@backstage/backend-common'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; describe('Pinniped - tokenCredentialRequest', () => { let app: ExtendedHttpServer; diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f3d10ba478..a8413f9931 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -115,6 +115,7 @@ }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 01a2fba6e8..e53f3a5dd9 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -47,7 +47,7 @@ import { } from '@backstage/plugin-permission-common'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha'; -import { MiddlewareFactory } from '@backstage/backend-app-api'; +import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; const mockAccess = jest.fn(); diff --git a/yarn.lock b/yarn.lock index b6cfe624cc..e34246fd58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6332,6 +6332,7 @@ __metadata: "@azure/identity": ^4.0.0 "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" @@ -6431,6 +6432,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -7179,6 +7181,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From 0c40cd7d40e48c4371098edd88d7a1833abcb2d7 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 10:59:55 +0200 Subject: [PATCH 195/393] chore: Update imports Signed-off-by: Johan Haals --- plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index d041fdc40b..5264e762b6 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -20,10 +20,10 @@ import { mockServices, startTestBackend, } from '@backstage/backend-test-utils'; -import { ExtendedHttpServer } from '@backstage/backend-defaults'; import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; import { createBackendModule } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; describe('resourcesRoutes', () => { let app: ExtendedHttpServer; From f24ba909bfdc4f6a86b0f3b712a8385e14df5828 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 11:34:39 +0200 Subject: [PATCH 196/393] backend-app-api: Move deprecated loadBackendConfig to backend-common Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-app-api/api-report.md | 19 ------- packages/backend-app-api/src/index.ts | 1 - packages/backend-common/api-report.md | 6 +++ .../backend-common/src/deprecated/config.ts | 54 ------------------- .../config/ObservableConfigProxy.test.ts | 0 .../config/ObservableConfigProxy.ts | 0 .../src/deprecated}/config/config.test.ts | 2 +- .../src/deprecated}/config/config.ts | 31 +++++++++-- .../src/deprecated}/config/index.ts | 2 +- .../src/deprecated/config}/urls.test.ts | 0 .../src/deprecated/config}/urls.ts | 0 .../src/schemas/rootLoggerServiceFactory.ts | 2 +- 12 files changed, 36 insertions(+), 81 deletions(-) delete mode 100644 packages/backend-common/src/deprecated/config.ts rename packages/{backend-app-api/src => backend-common/src/deprecated}/config/ObservableConfigProxy.test.ts (100%) rename packages/{backend-app-api/src => backend-common/src/deprecated}/config/ObservableConfigProxy.ts (100%) rename packages/{backend-app-api/src => backend-common/src/deprecated}/config/config.test.ts (100%) rename packages/{backend-app-api/src => backend-common/src/deprecated}/config/config.ts (76%) rename packages/{backend-app-api/src => backend-common/src/deprecated}/config/index.ts (94%) rename packages/{backend-app-api/src/lib => backend-common/src/deprecated/config}/urls.test.ts (100%) rename packages/{backend-app-api/src/lib => backend-common/src/deprecated/config}/urls.ts (100%) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index ba2a8734b0..bec20dff16 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -3,14 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import type { AppConfig } from '@backstage/config'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import type { Config } from '@backstage/config'; -import { ConfigSchema } from '@backstage/config-loader'; import { Format } from 'logform'; import { IdentityService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; -import { LoadConfigOptionsRemote } from '@backstage/config-loader'; import { LoggerService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; @@ -42,11 +38,6 @@ export interface Backend { stop(): Promise; } -// Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const createConfigSecretEnumerator: typeof createConfigSecretEnumerator_2; - // @public (undocumented) export function createSpecializedBackend( options: CreateSpecializedBackendOptions, @@ -72,16 +63,6 @@ export const identityServiceFactory: ServiceFactoryCompat< IdentityFactoryOptions >; -// @public @deprecated -export function loadBackendConfig(options: { - remote?: LoadConfigOptionsRemote; - argv: string[]; - additionalConfigs?: AppConfig[]; - watch?: boolean; -}): Promise<{ - config: Config; -}>; - // @public @deprecated (undocumented) export const tokenManagerServiceFactory: ServiceFactoryCompat< TokenManagerService, diff --git a/packages/backend-app-api/src/index.ts b/packages/backend-app-api/src/index.ts index 9fead85fe8..869cd64968 100644 --- a/packages/backend-app-api/src/index.ts +++ b/packages/backend-app-api/src/index.ts @@ -20,7 +20,6 @@ * @packageDocumentation */ -export * from './config'; export * from './logging'; export * from './wiring'; export * from './services/implementations'; diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index e63ff83318..32fdf769c7 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -21,6 +21,7 @@ import { CacheService } from '@backstage/backend-plugin-api'; import { CacheServiceOptions } from '@backstage/backend-plugin-api'; import { CacheServiceSetOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +import { ConfigSchema } from '@backstage/config-loader'; import cors from 'cors'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; @@ -133,6 +134,11 @@ export interface ContainerRunner { runContainer(opts: RunContainerOptions): Promise; } +// Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts +// +// @public @deprecated (undocumented) +export const createConfigSecretEnumerator: typeof createConfigSecretEnumerator_2; + // @public @deprecated export function createLegacyAuthAdapters< TOptions extends { diff --git a/packages/backend-common/src/deprecated/config.ts b/packages/backend-common/src/deprecated/config.ts deleted file mode 100644 index 2d06ca2072..0000000000 --- a/packages/backend-common/src/deprecated/config.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - createConfigSecretEnumerator, - loadBackendConfig as newLoadBackendConfig, -} from '../../../backend-app-api/src/config'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { AppConfig, Config } from '@backstage/config'; -import { LoadConfigOptionsRemote } from '@backstage/config-loader'; -import { setRootLoggerRedactionList } from './logging/createRootLogger'; - -/** - * Load configuration for a Backend. - * - * This function should only be called once, during the initialization of the backend. - * - * @public - * @deprecated Use {@link @backstage/backend-app-api#loadBackendConfig} instead. - */ -export async function loadBackendConfig(options: { - logger: LoggerService; - // process.argv or any other overrides - remote?: LoadConfigOptionsRemote; - additionalConfigs?: AppConfig[]; - argv: string[]; - watch?: boolean; -}): Promise { - const secretEnumerator = await createConfigSecretEnumerator({ - logger: options.logger, - }); - const { config } = await newLoadBackendConfig(options); - - setRootLoggerRedactionList(secretEnumerator(config)); - config.subscribe?.(() => - setRootLoggerRedactionList(secretEnumerator(config)), - ); - - return config; -} diff --git a/packages/backend-app-api/src/config/ObservableConfigProxy.test.ts b/packages/backend-common/src/deprecated/config/ObservableConfigProxy.test.ts similarity index 100% rename from packages/backend-app-api/src/config/ObservableConfigProxy.test.ts rename to packages/backend-common/src/deprecated/config/ObservableConfigProxy.test.ts diff --git a/packages/backend-app-api/src/config/ObservableConfigProxy.ts b/packages/backend-common/src/deprecated/config/ObservableConfigProxy.ts similarity index 100% rename from packages/backend-app-api/src/config/ObservableConfigProxy.ts rename to packages/backend-common/src/deprecated/config/ObservableConfigProxy.ts diff --git a/packages/backend-app-api/src/config/config.test.ts b/packages/backend-common/src/deprecated/config/config.test.ts similarity index 100% rename from packages/backend-app-api/src/config/config.test.ts rename to packages/backend-common/src/deprecated/config/config.test.ts index 8828c557e0..865b1b4d60 100644 --- a/packages/backend-app-api/src/config/config.test.ts +++ b/packages/backend-common/src/deprecated/config/config.test.ts @@ -15,8 +15,8 @@ */ import { loadConfigSchema } from '@backstage/config-loader'; -import { createConfigSecretEnumerator } from './config'; import { mockServices } from '@backstage/backend-test-utils'; +import { createConfigSecretEnumerator } from './config'; describe('createConfigSecretEnumerator', () => { it('should enumerate secrets', async () => { diff --git a/packages/backend-app-api/src/config/config.ts b/packages/backend-common/src/deprecated/config/config.ts similarity index 76% rename from packages/backend-app-api/src/config/config.ts rename to packages/backend-common/src/deprecated/config/config.ts index 957997849e..d50476a9f2 100644 --- a/packages/backend-app-api/src/config/config.ts +++ b/packages/backend-common/src/deprecated/config/config.ts @@ -14,8 +14,11 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { AppConfig, Config } from '@backstage/config'; +import { setRootLoggerRedactionList } from '../logging/createRootLogger'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { createConfigSecretEnumerator as _createConfigSecretEnumerator } from '../../../backend-defaults/src/entrypoints/rootConfig/createConfigSecretEnumerator'; +import { createConfigSecretEnumerator as _createConfigSecretEnumerator } from '../../../../backend-defaults/src/entrypoints/rootConfig/createConfigSecretEnumerator'; import { resolve as resolvePath } from 'path'; import parseArgs from 'minimist'; @@ -26,13 +29,12 @@ import { LoadConfigOptionsRemote, } from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; -import type { Config, AppConfig } from '@backstage/config'; import { ObservableConfigProxy } from './ObservableConfigProxy'; -import { isValidUrl } from '../lib/urls'; +import { isValidUrl } from './urls'; /** * @public - * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. + * @deprecated Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the {@link @backstage/config-loader#ConfigSources} facilities if required. */ export const createConfigSecretEnumerator = _createConfigSecretEnumerator; @@ -45,6 +47,27 @@ export const createConfigSecretEnumerator = _createConfigSecretEnumerator; * @deprecated Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the {@link @backstage/config-loader#ConfigSources} facilities if required. */ export async function loadBackendConfig(options: { + logger: LoggerService; + // process.argv or any other overrides + remote?: LoadConfigOptionsRemote; + additionalConfigs?: AppConfig[]; + argv: string[]; + watch?: boolean; +}): Promise { + const secretEnumerator = await createConfigSecretEnumerator({ + logger: options.logger, + }); + const { config } = await newLoadBackendConfig(options); + + setRootLoggerRedactionList(secretEnumerator(config)); + config.subscribe?.(() => + setRootLoggerRedactionList(secretEnumerator(config)), + ); + + return config; +} + +async function newLoadBackendConfig(options: { remote?: LoadConfigOptionsRemote; argv: string[]; additionalConfigs?: AppConfig[]; diff --git a/packages/backend-app-api/src/config/index.ts b/packages/backend-common/src/deprecated/config/index.ts similarity index 94% rename from packages/backend-app-api/src/config/index.ts rename to packages/backend-common/src/deprecated/config/index.ts index 0ed8d2bc93..c87193876c 100644 --- a/packages/backend-app-api/src/config/index.ts +++ b/packages/backend-common/src/deprecated/config/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-app-api/src/lib/urls.test.ts b/packages/backend-common/src/deprecated/config/urls.test.ts similarity index 100% rename from packages/backend-app-api/src/lib/urls.test.ts rename to packages/backend-common/src/deprecated/config/urls.test.ts diff --git a/packages/backend-app-api/src/lib/urls.ts b/packages/backend-common/src/deprecated/config/urls.ts similarity index 100% rename from packages/backend-app-api/src/lib/urls.ts rename to packages/backend-common/src/deprecated/config/urls.ts diff --git a/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts b/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts index 6681a65a75..bf37ac4a32 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts @@ -20,7 +20,7 @@ import { } from '@backstage/backend-plugin-api'; import { WinstonLogger } from '@backstage/backend-app-api'; import { transports, format } from 'winston'; -import { createConfigSecretEnumerator } from '@backstage/backend-app-api'; +import { createConfigSecretEnumerator } from '@backstage/backend-common'; import { loadConfigSchema } from '@backstage/config-loader'; import { getPackages } from '@manypkg/get-packages'; import { dynamicPluginsSchemasServiceRef } from './schemas'; From 87cd3d0b9ec6d4ef8ea3797d7e0a8d237334c0cb Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 13:41:17 +0200 Subject: [PATCH 197/393] backend-defaults: Added deprecation warning for the dangerouslyDisableDefaultAuthPolicy config option Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- docs/tutorials/auth-service-migration.md | 2 ++ .../src/entrypoints/auth/DefaultAuthService.ts | 5 +++++ .../src/entrypoints/auth/authServiceFactory.ts | 1 + 3 files changed, 8 insertions(+) diff --git a/docs/tutorials/auth-service-migration.md b/docs/tutorials/auth-service-migration.md index 9e2633a12a..d12b0d5906 100644 --- a/docs/tutorials/auth-service-migration.md +++ b/docs/tutorials/auth-service-migration.md @@ -24,6 +24,8 @@ backend: dangerouslyDisableDefaultAuthPolicy: true ``` +Please note that this functionality will be removed in a future release, and you should migrate to using the new auth services as soon as possible or you would have to support your own service for issuing tokens. + In short, this will allow requests through to plugins in your backend, even if they do not include any credentials. The requests will still be treated as unauthenticated however, which not all plugin endpoints may accept. For more information on the impact of this configuration, see the [auth service documentation](../backend-system/core-services/auth.md). ### Migrating the backend diff --git a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts index 159da79435..1b2077e9a8 100644 --- a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts +++ b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts @@ -22,6 +22,7 @@ import { BackstagePrincipalTypes, BackstageServicePrincipal, BackstageUserPrincipal, + LoggerService, } from '@backstage/backend-plugin-api'; import { AuthenticationError, ForwardedError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; @@ -47,6 +48,7 @@ export class DefaultAuthService implements AuthService { private readonly pluginId: string, private readonly disableDefaultAuthPolicy: boolean, private readonly pluginKeySource: PluginKeySource, + private readonly logger: LoggerService, ) {} async authenticate( @@ -166,6 +168,9 @@ export class DefaultAuthService implements AuthService { }); } // If the target plugin does not support the new auth service, fall back to using old token format + this.logger.warn( + 'tokenManager is DEPRECATED, please migrate to the new auth service, see https://backstage.io/docs/tutorials/auth-service-migration for more information', + ); return this.tokenManager.getToken().catch(error => { throw new ForwardedError( `Unable to generate legacy token for communication with the '${targetPluginId}' plugin. ` + diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts index e275e2be00..b66ba7227f 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts @@ -88,6 +88,7 @@ export const authServiceFactory = createServiceFactory({ plugin.getId(), disableDefaultAuthPolicy, keySource, + logger, ); }, }); From 70e14b8ed21bff553ec6fadb9497d03cafecd114 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 13:52:45 +0200 Subject: [PATCH 198/393] backend-defaults: Stop accepting callback BackendFeatures for `backend.add` Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-app-api/api-report.md | 8 ------- .../src/wiring/BackstageBackend.ts | 21 +++---------------- packages/backend-app-api/src/wiring/types.ts | 12 ----------- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index bec20dff16..46d7d9615f 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -24,14 +24,6 @@ export interface Backend { default: BackendFeature; }>, ): void; - // @deprecated (undocumented) - add( - feature: - | (() => BackendFeature) - | Promise<{ - default: () => BackendFeature; - }>, - ): void; // (undocumented) start(): Promise; // (undocumented) diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts index 0dd3ba4423..9df7549bcb 100644 --- a/packages/backend-app-api/src/wiring/BackstageBackend.ts +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -25,12 +25,7 @@ export class BackstageBackend implements Backend { this.#initializer = new BackendInitializer(defaultServiceFactories); } - add( - feature: - | BackendFeature - | (() => BackendFeature) - | Promise<{ default: BackendFeature | (() => BackendFeature) }>, - ): void { + add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void { if (isPromise(feature)) { this.#initializer.add(feature.then(f => unwrapFeature(f.default))); } else { @@ -57,15 +52,8 @@ function isPromise(value: unknown | Promise): value is Promise { } function unwrapFeature( - feature: - | BackendFeature - | (() => BackendFeature) - | { default: BackendFeature | (() => BackendFeature) }, + feature: BackendFeature | { default: BackendFeature }, ): BackendFeature { - if (typeof feature === 'function') { - return feature(); - } - if ('$$type' in feature) { return feature; } @@ -75,10 +63,7 @@ function unwrapFeature( // when importing using a dynamic import. // TODO: This is a broader issue than just this piece of code, and should move away from CommonJS. if ('default' in feature) { - const defaultFeature = feature.default; - return typeof defaultFeature === 'function' - ? defaultFeature() - : defaultFeature; + return feature.default; } return feature; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 075eb58b3f..bd761ff3c8 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -26,18 +26,6 @@ import { */ export interface Backend { add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void; - /** - * @deprecated The ability to add features defined as a callback is being - * removed. Please update the installed feature to no longer be defined as a - * callback, typically this means updating it to use the latest version of - * `@backstage/backend-plugin-api`. If the feature is from a third-party - * package, please reach out to the package maintainer to update it. - */ - add( - feature: - | (() => BackendFeature) - | Promise<{ default: () => BackendFeature }>, - ): void; start(): Promise; stop(): Promise; } From 070828a4f40fb03c779ed1f4de41115df151bd77 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:07:29 +0200 Subject: [PATCH 199/393] backend-defaults: remove unused dependencies Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-app-api/package.json | 2 -- yarn.lock | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 76261adcd2..588a969390 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -62,8 +62,6 @@ "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@types/cors": "^2.8.6", - "@types/express": "^4.17.6", "compression": "^1.7.4", "cookie": "^0.6.0", "cors": "^2.8.5", diff --git a/yarn.lock b/yarn.lock index e34246fd58..edab1f5580 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3478,8 +3478,6 @@ __metadata: "@backstage/types": "workspace:^" "@manypkg/get-packages": ^1.1.3 "@types/compression": ^1.7.0 - "@types/cors": ^2.8.6 - "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 "@types/http-errors": ^2.0.0 "@types/minimist": ^1.2.0 From 5cee28573e9112b1d2b33b117dab5b855a64eb8e Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:11:05 +0200 Subject: [PATCH 200/393] Delete WinstonLogger from backend-defaults, instead import from backend-defaults Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-app-api/api-report.md | 32 ------- packages/backend-app-api/src/index.ts | 1 - .../src/logging/WinstonLogger.ts | 92 ------------------- packages/backend-app-api/src/logging/index.ts | 18 ---- .../deprecated/logging/createRootLogger.ts | 2 +- .../package.json | 31 +++---- .../src/schemas/rootLoggerServiceFactory.ts | 2 +- 7 files changed, 16 insertions(+), 162 deletions(-) delete mode 100644 packages/backend-app-api/src/logging/WinstonLogger.ts delete mode 100644 packages/backend-app-api/src/logging/index.ts diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 46d7d9615f..c7b47cb1a0 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -4,15 +4,10 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { Format } from 'logform'; import { IdentityService } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { RootLoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; -import { transport } from 'winston'; // @public (undocumented) export interface Backend { @@ -62,31 +57,4 @@ export const tokenManagerServiceFactory: ServiceFactoryCompat< 'singleton', undefined >; - -// @public @deprecated -export class WinstonLogger implements RootLoggerService { - // (undocumented) - addRedactions(redactions: Iterable): void; - // (undocumented) - child(meta: JsonObject): LoggerService; - static colorFormat(): Format; - static create(options: WinstonLoggerOptions): WinstonLogger; - // (undocumented) - debug(message: string, meta?: JsonObject): void; - // (undocumented) - error(message: string, meta?: JsonObject): void; - // (undocumented) - info(message: string, meta?: JsonObject): void; - static redacter(): { - format: Format; - add: (redactions: Iterable) => void; - }; - // (undocumented) - warn(message: string, meta?: JsonObject): void; -} - -// Warning: (ae-forgotten-export) The symbol "WinstonLoggerOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type WinstonLoggerOptions = WinstonLoggerOptions_2; ``` diff --git a/packages/backend-app-api/src/index.ts b/packages/backend-app-api/src/index.ts index 869cd64968..02633f3732 100644 --- a/packages/backend-app-api/src/index.ts +++ b/packages/backend-app-api/src/index.ts @@ -20,6 +20,5 @@ * @packageDocumentation */ -export * from './logging'; export * from './wiring'; export * from './services/implementations'; diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts deleted file mode 100644 index e5403906ce..0000000000 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - WinstonLogger as _WinstonLogger, - type WinstonLoggerOptions as _WinstonLoggerOptions, -} from '../../../backend-defaults/src/entrypoints/rootLogger'; - -import { - LoggerService, - RootLoggerService, -} from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; -import { Format } from 'logform'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead. - */ -export type WinstonLoggerOptions = _WinstonLoggerOptions; - -/** - * A {@link @backstage/backend-plugin-api#LoggerService} implementation based on winston. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead. - */ -export class WinstonLogger implements RootLoggerService { - /** - * Creates a {@link WinstonLogger} instance. - */ - static create(options: WinstonLoggerOptions): WinstonLogger { - return new WinstonLogger(_WinstonLogger.create(options)); - } - - /** - * Creates a winston log formatter for redacting secrets. - */ - static redacter(): { - format: Format; - add: (redactions: Iterable) => void; - } { - return _WinstonLogger.redacter(); - } - - /** - * Creates a pretty printed winston log formatter. - */ - static colorFormat(): Format { - return _WinstonLogger.colorFormat(); - } - - private constructor(private readonly impl: _WinstonLogger) {} - - error(message: string, meta?: JsonObject): void { - this.impl.error(message, meta); - } - - warn(message: string, meta?: JsonObject): void { - this.impl.warn(message, meta); - } - - info(message: string, meta?: JsonObject): void { - this.impl.info(message, meta); - } - - debug(message: string, meta?: JsonObject): void { - this.impl.debug(message, meta); - } - - child(meta: JsonObject): LoggerService { - return this.impl.child(meta); - } - - addRedactions(redactions: Iterable) { - this.impl.addRedactions(redactions); - } -} diff --git a/packages/backend-app-api/src/logging/index.ts b/packages/backend-app-api/src/logging/index.ts deleted file mode 100644 index 14fe33f898..0000000000 --- a/packages/backend-app-api/src/logging/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { WinstonLogger } from './WinstonLogger'; -export type { WinstonLoggerOptions } from './WinstonLogger'; diff --git a/packages/backend-common/src/deprecated/logging/createRootLogger.ts b/packages/backend-common/src/deprecated/logging/createRootLogger.ts index b177024601..04b3799381 100644 --- a/packages/backend-common/src/deprecated/logging/createRootLogger.ts +++ b/packages/backend-common/src/deprecated/logging/createRootLogger.ts @@ -15,7 +15,7 @@ */ // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { WinstonLogger } from '../../../../backend-app-api/src/logging/WinstonLogger'; +import { WinstonLogger } from '../../../../backend-defaults/src/entrypoints/rootLogger/WinstonLogger'; import { merge } from 'lodash'; import * as winston from 'winston'; import { format, LoggerOptions } from 'winston'; diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index f4a3237762..a4a4a1cdb5 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -7,14 +7,16 @@ "publishConfig": { "access": "public" }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/backend-dynamic-feature-service" }, - "backstage": { - "role": "node-library" - }, + "license": "Apache-2.0", "exports": { ".": "./src/index.ts", "./package.json": "./package.json" @@ -26,23 +28,23 @@ ] } }, - "homepage": "https://backstage.io", - "keywords": [ - "backstage" + "files": [ + "dist", + "config.d.ts" ], - "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", @@ -70,13 +72,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" - }, - "files": [ - "dist", - "config.d.ts" - ] -} + } +} \ No newline at end of file diff --git a/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts b/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts index bf37ac4a32..00d5c85e6e 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts @@ -18,7 +18,7 @@ import { createServiceFactory, coreServices, } from '@backstage/backend-plugin-api'; -import { WinstonLogger } from '@backstage/backend-app-api'; +import { WinstonLogger } from '@backstage/backend-defaults/rootLogger'; import { transports, format } from 'winston'; import { createConfigSecretEnumerator } from '@backstage/backend-common'; import { loadConfigSchema } from '@backstage/config-loader'; From da4fde5cc241ee7833b5450d9bde31bd4397fd73 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:26:43 +0200 Subject: [PATCH 201/393] backend-app-api: Add changeset Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- .changeset/brown-mails-drum.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/brown-mails-drum.md diff --git a/.changeset/brown-mails-drum.md b/.changeset/brown-mails-drum.md new file mode 100644 index 0000000000..0c87a5cbaf --- /dev/null +++ b/.changeset/brown-mails-drum.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +Removed several deprecated service factories. These can instead be imported from `@backstage/backend-defaults` package. From bfdfac62305473373e87582d84655841b6c1c6d3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:26:53 +0200 Subject: [PATCH 202/393] Revert "refactor: remove deprecated url reader service types" This reverts commit c32b5f3467cf6d0bcd355fde0fb0e4bce77f1b38. Signed-off-by: Johan Haals --- packages/backend-common/api-report.md | 36 +++++++++++ .../backend-common/src/deprecated/index.ts | 63 +++++++++++++++++++ packages/backend-plugin-api/api-report.md | 28 +++++++++ .../services/definitions/UrlReaderService.ts | 47 ++++++++++++++ .../src/services/definitions/index.ts | 9 +++ plugins/search-backend-node/api-report.md | 4 +- plugins/search-backend-node/package.json | 3 +- ...ewlineDelimitedJsonCollatorFactory.test.ts | 14 ++--- .../NewlineDelimitedJsonCollatorFactory.ts | 7 ++- plugins/techdocs-node/src/helpers.test.ts | 24 +++---- yarn.lock | 2 +- 11 files changed, 210 insertions(+), 27 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 32fdf769c7..11a5befad4 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -49,12 +49,21 @@ import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; import { ReadCommitResult } from 'isomorphic-git'; +import { ReadTreeOptions as ReadTreeOptions_2 } from '@backstage/backend-plugin-api'; +import { ReadTreeResponse as ReadTreeResponse_2 } from '@backstage/backend-plugin-api'; +import { ReadTreeResponseDirOptions as ReadTreeResponseDirOptions_2 } from '@backstage/backend-plugin-api'; +import { ReadTreeResponseFile as ReadTreeResponseFile_2 } from '@backstage/backend-plugin-api'; +import { ReadUrlOptions as ReadUrlOptions_2 } from '@backstage/backend-plugin-api'; +import { ReadUrlResponse as ReadUrlResponse_2 } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SearchOptions as SearchOptions_2 } from '@backstage/backend-plugin-api'; +import { SearchResponse as SearchResponse_2 } from '@backstage/backend-plugin-api'; +import { SearchResponseFile as SearchResponseFile_2 } from '@backstage/backend-plugin-api'; import { Server } from 'http'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; @@ -502,6 +511,15 @@ export interface PullOptions { // @public @deprecated (undocumented) export type ReaderFactory = ReaderFactory_2; +// @public @deprecated (undocumented) +export type ReadTreeOptions = ReadTreeOptions_2; + +// @public @deprecated (undocumented) +export type ReadTreeResponse = ReadTreeResponse_2; + +// @public @deprecated (undocumented) +export type ReadTreeResponseDirOptions = ReadTreeResponseDirOptions_2; + // Warning: (ae-forgotten-export) The symbol "ReadTreeResponseFactory_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -512,6 +530,15 @@ export type ReadTreeResponseFactory = ReadTreeResponseFactory_2; // @public @deprecated (undocumented) export type ReadTreeResponseFactoryOptions = ReadTreeResponseFactoryOptions_2; +// @public @deprecated (undocumented) +export type ReadTreeResponseFile = ReadTreeResponseFile_2; + +// @public @deprecated (undocumented) +export type ReadUrlOptions = ReadUrlOptions_2; + +// @public @deprecated (undocumented) +export type ReadUrlResponse = ReadUrlResponse_2; + // Warning: (ae-forgotten-export) The symbol "ReadUrlResponseFactory_2" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -556,6 +583,15 @@ export type RunContainerOptions = { pullOptions?: PullOptions; }; +// @public @deprecated (undocumented) +export type SearchOptions = SearchOptions_2; + +// @public @deprecated (undocumented) +export type SearchResponse = SearchResponse_2; + +// @public @deprecated (undocumented) +export type SearchResponseFile = SearchResponseFile_2; + // @public @deprecated export class ServerTokenManager implements TokenManager { // (undocumented) diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index 8c37017eb7..e3e4101d25 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -85,6 +85,15 @@ import { resolvePackagePath as _resolvePackagePath, resolveSafeChildPath as _resolveSafeChildPath, isChildPath as _isChildPath, + ReadTreeOptions as _ReadTreeOptions, + ReadTreeResponse as _ReadTreeResponse, + ReadTreeResponseFile as _ReadTreeResponseFile, + ReadTreeResponseDirOptions as _ReadTreeResponseDirOptions, + ReadUrlOptions as _ReadUrlOptions, + ReadUrlResponse as _ReadUrlResponse, + SearchOptions as _SearchOptions, + SearchResponse as _SearchResponse, + SearchResponseFile as _SearchResponseFile, UrlReaderService as _UrlReaderService, LifecycleService, PluginMetadataService, @@ -403,6 +412,60 @@ export type ReadUrlResponseFactoryFromStreamOptions = */ export type UrlReaderPredicateTuple = _UrlReaderPredicateTuple; +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead + */ +export type ReadTreeOptions = _ReadTreeOptions; + +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead + */ +export type ReadTreeResponse = _ReadTreeResponse; + +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead + */ +export type ReadTreeResponseFile = _ReadTreeResponseFile; + +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead + */ +export type ReadTreeResponseDirOptions = _ReadTreeResponseDirOptions; + +/** + * @public + * @deprecated Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead + */ +export type ReadUrlOptions = _ReadUrlOptions; + +/** + * @public + * @deprecated Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead + */ +export type ReadUrlResponse = _ReadUrlResponse; + +/** + * @public + * @deprecated Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead + */ +export type SearchOptions = _SearchOptions; + +/** + * @public + * @deprecated Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead + */ +export type SearchResponse = _SearchResponse; + +/** + * @public + * @deprecated Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead + */ +export type SearchResponseFile = _SearchResponseFile; + /** * @public * @deprecated Use `UrlReaderService` from `@backstage/backend-plugin-api` instead diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 0864064625..319ddf0bc6 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -553,6 +553,25 @@ export function readSchedulerServiceTaskScheduleDefinitionFromConfig( config: Config, ): SchedulerServiceTaskScheduleDefinition; +// @public @deprecated (undocumented) +export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; + +// @public @deprecated (undocumented) +export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; + +// @public @deprecated (undocumented) +export type ReadTreeResponseDirOptions = + UrlReaderServiceReadTreeResponseDirOptions; + +// @public @deprecated (undocumented) +export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; + +// @public @deprecated (undocumented) +export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; + +// @public @deprecated (undocumented) +export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; + // @public export function resolvePackagePath(name: string, ...paths: string[]): string; @@ -688,6 +707,15 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig { timeout: string | HumanDuration; } +// @public @deprecated (undocumented) +export type SearchOptions = UrlReaderServiceSearchOptions; + +// @public @deprecated (undocumented) +export type SearchResponse = UrlReaderServiceSearchResponse; + +// @public @deprecated (undocumented) +export type SearchResponseFile = UrlReaderServiceSearchResponseFile; + // @public (undocumented) export interface ServiceFactory< TService = unknown, diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index 9b327cfbf0..25b787107e 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -368,3 +368,50 @@ export type UrlReaderServiceSearchResponseFile = { */ lastModifiedAt?: Date; }; + +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeOptions` instead + */ +export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeResponse` instead + */ +export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` instead + */ +export type ReadTreeResponseDirOptions = + UrlReaderServiceReadTreeResponseDirOptions; +/** + * @public + * @deprecated Use `UrlReaderServiceReadTreeResponseFile` instead + */ +export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; +/** + * @public + * @deprecated Use `UrlReaderServiceReadUrlResponse` instead + */ +export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; +/** + * @public + * @deprecated Use `UrlReaderServiceReadUrlOptions` instead + */ +export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; +/** + * @public + * @deprecated Use `UrlReaderServiceSearchOptions` instead + */ +export type SearchOptions = UrlReaderServiceSearchOptions; +/** + * @public + * @deprecated Use `UrlReaderServiceSearchResponse` instead + */ +export type SearchResponse = UrlReaderServiceSearchResponse; +/** + * @public + * @deprecated Use `UrlReaderServiceSearchResponseFile` instead + */ +export type SearchResponseFile = UrlReaderServiceSearchResponseFile; diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 194658dc0e..5e30d444fd 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -66,6 +66,15 @@ export type { } from './SchedulerService'; export type { TokenManagerService } from './TokenManagerService'; export type { + ReadTreeOptions, + ReadTreeResponse, + ReadTreeResponseDirOptions, + ReadTreeResponseFile, + ReadUrlResponse, + ReadUrlOptions, + SearchOptions, + SearchResponse, + SearchResponseFile, UrlReaderServiceReadTreeOptions, UrlReaderServiceReadTreeResponse, UrlReaderServiceReadTreeResponseDirOptions, diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index 03269c72e8..5e8961a65e 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -20,7 +20,7 @@ import { SearchQuery } from '@backstage/plugin-search-common'; import { TaskFunction } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; import { Transform } from 'stream'; -import { UrlReaderService } from '@backstage/backend-plugin-api'; +import { UrlReader } from '@backstage/backend-common'; import { Writable } from 'stream'; // @public @@ -138,7 +138,7 @@ export class NewlineDelimitedJsonCollatorFactory export type NewlineDelimitedJsonCollatorFactoryOptions = { type: string; searchPattern: string; - reader: UrlReaderService; + reader: UrlReader; logger: LoggerService; visibilityPermission?: Permission; }; diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index c8a833e92a..d7a7191faa 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -53,7 +53,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", @@ -67,6 +67,7 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/ndjson": "^2.0.1" diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts index 45f3635232..45cc98e9ba 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts @@ -14,16 +14,16 @@ * limitations under the License. */ +import { + ReadUrlResponse, + UrlReader, + UrlReaders, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Readable } from 'stream'; import { NewlineDelimitedJsonCollatorFactory } from './NewlineDelimitedJsonCollatorFactory'; import { TestPipeline } from '../test-utils'; import { mockServices } from '@backstage/backend-test-utils'; -import { - UrlReaderService, - UrlReaderServiceReadUrlResponse, -} from '@backstage/backend-plugin-api'; -import { UrlReaders } from '@backstage/backend-defaults/urlReader'; describe('DefaultCatalogCollatorFactory', () => { const config = new ConfigReader({}); @@ -42,9 +42,7 @@ describe('DefaultCatalogCollatorFactory', () => { describe('getCollator', () => { let readable: Readable; let reader: jest.Mocked< - UrlReaderService & { - readUrl: jest.Mock>; - } + UrlReader & { readUrl: jest.Mock> } >; let factory: NewlineDelimitedJsonCollatorFactory; diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index bf78b98040..e7d749dd41 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -18,8 +18,9 @@ import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Permission } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; +import { UrlReader } from '@backstage/backend-common'; import { parse as parseNdjson } from 'ndjson'; -import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * Options for instantiate NewlineDelimitedJsonCollatorFactory @@ -28,7 +29,7 @@ import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; export type NewlineDelimitedJsonCollatorFactoryOptions = { type: string; searchPattern: string; - reader: UrlReaderService; + reader: UrlReader; logger: LoggerService; visibilityPermission?: Permission; }; @@ -72,7 +73,7 @@ export class NewlineDelimitedJsonCollatorFactory private constructor( type: string, private readonly searchPattern: string, - private readonly reader: UrlReaderService, + private readonly reader: UrlReader, private readonly logger: LoggerService, visibilityPermission: Permission | undefined, ) { diff --git a/plugins/techdocs-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index e0c9ac41ca..4f77a5432b 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -14,6 +14,13 @@ * limitations under the License. */ +import { + ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, + SearchResponse, + UrlReader, +} from '@backstage/backend-common'; import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -27,13 +34,6 @@ import { parseReferenceAnnotation, transformDirLocation, } from './helpers'; -import { - UrlReaderService, - UrlReaderServiceReadTreeResponse, - UrlReaderServiceReadUrlOptions, - UrlReaderServiceReadUrlResponse, - UrlReaderServiceSearchResponse, -} from '@backstage/backend-plugin-api'; jest.mock('@backstage/catalog-model', () => ({ ...jest.requireActual('@backstage/catalog-model'), @@ -290,19 +290,19 @@ describe('getLocationForEntity', () => { describe('getDocFilesFromRepository', () => { it('should read a remote directory using UrlReader.readTree', async () => { - class MockUrlReader implements UrlReaderService { + class MockUrlReader implements UrlReader { async read() { return Buffer.from('mock'); } async readUrl( _url: string, - _options?: UrlReaderServiceReadUrlOptions | undefined, - ): Promise { + _options?: ReadUrlOptions | undefined, + ): Promise { throw new Error('Method not implemented.'); } - async readTree(): Promise { + async readTree(): Promise { return { dir: async () => { return '/tmp/testfolder'; @@ -317,7 +317,7 @@ describe('getDocFilesFromRepository', () => { }; } - async search(): Promise { + async search(): Promise { return { etag: '', files: [], diff --git a/yarn.lock b/yarn.lock index edab1f5580..4c47bc5771 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7554,7 +7554,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-search-backend-node@workspace:plugins/search-backend-node" dependencies: - "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From 30691778a450c1143be3c3d22cc2329cb483cfd9 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:29:02 +0200 Subject: [PATCH 203/393] chore: fix newline Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-dynamic-feature-service/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index a4a4a1cdb5..18501623a3 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,9 +1,7 @@ { "name": "@backstage/backend-dynamic-feature-service", - "description": "Backstage dynamic feature service", "version": "0.2.16-next.3", - "main": "src/index.ts", - "types": "src/index.ts", + "description": "Backstage dynamic feature service", "publishConfig": { "access": "public" }, @@ -21,6 +19,8 @@ ".": "./src/index.ts", "./package.json": "./package.json" }, + "main": "src/index.ts", + "types": "src/index.ts", "typesVersions": { "*": { "package.json": [ @@ -76,4 +76,4 @@ "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" } -} \ No newline at end of file +} From b63d378bb161c964c3e34f470becd6f81bc84aaa Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:35:57 +0200 Subject: [PATCH 204/393] Add changesets Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- .changeset/four-singers-fetch.md | 5 +++++ .changeset/tidy-guests-battle.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/four-singers-fetch.md create mode 100644 .changeset/tidy-guests-battle.md diff --git a/.changeset/four-singers-fetch.md b/.changeset/four-singers-fetch.md new file mode 100644 index 0000000000..fa3a035c24 --- /dev/null +++ b/.changeset/four-singers-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Update imports diff --git a/.changeset/tidy-guests-battle.md b/.changeset/tidy-guests-battle.md new file mode 100644 index 0000000000..df86b02d1d --- /dev/null +++ b/.changeset/tidy-guests-battle.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +export `createConfigSecretEnumerator` from `@backstage/backend-common` instead of `@backstage/backend-app-api`. From 3b429fb71cd836e8a7621baf9539f8eb968e10a5 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:40:53 +0200 Subject: [PATCH 205/393] Update changets Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- .changeset/four-singers-fetch.md | 6 +++++- .changeset/friendly-points-approve.md | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/friendly-points-approve.md diff --git a/.changeset/four-singers-fetch.md b/.changeset/four-singers-fetch.md index fa3a035c24..a58f5de8aa 100644 --- a/.changeset/four-singers-fetch.md +++ b/.changeset/four-singers-fetch.md @@ -1,5 +1,9 @@ --- '@backstage/backend-test-utils': patch +'@backstage/plugin-kubernetes-node': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-proxy-backend': patch --- -Update imports +Update internal imports diff --git a/.changeset/friendly-points-approve.md b/.changeset/friendly-points-approve.md new file mode 100644 index 0000000000..52de593750 --- /dev/null +++ b/.changeset/friendly-points-approve.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': patch +--- + +Added deprecation warning to urge users to perform the auth service migration or implement their own token manager service. +See https://backstage.io/docs/tutorials/auth-service-migration for more information. From 7cbaf4aab13f9d0326a5d5d05ecf9be05d302e00 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:44:16 +0200 Subject: [PATCH 206/393] chore: re-add package role Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- packages/backend-dynamic-feature-service/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 18501623a3..56474e4a65 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -2,6 +2,9 @@ "name": "@backstage/backend-dynamic-feature-service", "version": "0.2.16-next.3", "description": "Backstage dynamic feature service", + "backstage": { + "role": "node-library" + }, "publishConfig": { "access": "public" }, From 029dab1c67f04fe6bc951d8cc1206e2b1f8c14f7 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 14:46:08 +0200 Subject: [PATCH 207/393] chore: Add another package to changeset Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- .changeset/four-singers-fetch.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/four-singers-fetch.md b/.changeset/four-singers-fetch.md index a58f5de8aa..69fe6f77bf 100644 --- a/.changeset/four-singers-fetch.md +++ b/.changeset/four-singers-fetch.md @@ -4,6 +4,7 @@ '@backstage/plugin-kubernetes-backend': patch '@backstage/plugin-scaffolder-backend': patch '@backstage/plugin-proxy-backend': patch +'@backstage/backend-dynamic-feature-service': patch --- Update internal imports From d55b8e317dddcf26870726662c95bdcda3fb5d6f Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 14 Aug 2024 14:13:18 +0200 Subject: [PATCH 208/393] Avoid false check for broadcast emails Signed-off-by: Marek Libra --- .changeset/flat-guests-behave.md | 6 ++++++ .../src/processor/NotificationsEmailProcessor.ts | 13 ++++++++++--- .../src/service/getUsersForEntityRef.ts | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 .changeset/flat-guests-behave.md diff --git a/.changeset/flat-guests-behave.md b/.changeset/flat-guests-behave.md new file mode 100644 index 0000000000..ad4b238e03 --- /dev/null +++ b/.changeset/flat-guests-behave.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-notifications-backend': patch +--- + +Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 03388a77a2..a876a548d2 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -207,12 +207,17 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private async getRecipientEmails( notification: Notification, options: NotificationSendOptions, - ) { + ): Promise { let emails: string[]; - if (options.recipients.type === 'broadcast' || notification.user === null) { + if (options.recipients.type === 'broadcast') { emails = await this.getBroadcastEmails(); - } else { + } else if (options.recipients.type === 'entity' && !!notification.user) { emails = await this.getUserEmail(notification.user); + } else { + this.logger.info( + `Unknown notification type ${options.recipients.type} or missing user.`, + ); + return []; } if (this.allowlistEmailAddresses) { @@ -338,6 +343,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return; } + this.logger.debug(`Sending notification emails to: ${emails.join(',')}`); + if (!this.templateRenderer) { await this.sendPlainEmail(notification, emails); return; diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts index fd564d6528..9ced24709b 100644 --- a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts @@ -157,5 +157,5 @@ export const getUsersForEntityRef = async ( users.push(...u); } - return [...new Set(users)]; + return [...new Set(users)].filter(Boolean); }; From 861f162b4a39117b824669d67a951ed1db142e3d Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 15:17:03 +0200 Subject: [PATCH 209/393] backend-test-utils: Remove deprecations Co-authored-by: Camila Belo Signed-off-by: Johan Haals --- .changeset/late-timers-sort.md | 12 +++++ packages/backend-test-utils/api-report.md | 17 ------- packages/backend-test-utils/src/deprecated.ts | 45 ------------------- packages/backend-test-utils/src/index.ts | 1 - .../src/next/wiring/ServiceFactoryTester.ts | 11 ----- 5 files changed, 12 insertions(+), 74 deletions(-) create mode 100644 .changeset/late-timers-sort.md delete mode 100644 packages/backend-test-utils/src/deprecated.ts diff --git a/.changeset/late-timers-sort.md b/.changeset/late-timers-sort.md new file mode 100644 index 0000000000..1b5221721c --- /dev/null +++ b/.changeset/late-timers-sort.md @@ -0,0 +1,12 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Removed these deprecated helpers: + +- `setupRequestMockHandlers` Use `CreateMockDirectoryOptions` instead. +- `CreateMockDirectoryOptions` Use `registerMswTestHooks` instead. + +Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. + +Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 584fedb1f5..d3a80c419e 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -55,9 +55,6 @@ export interface CreateMockDirectoryOptions { mockOsTmpDir?: boolean; } -// @public @deprecated (undocumented) -export function isDockerDisabledForTests(): boolean; - // @public (undocumented) export namespace mockCredentials { export function limitedUser( @@ -143,9 +140,6 @@ export interface MockDirectoryContentOptions { shouldReadAsText?: boolean | ((path: string, buffer: Buffer) => boolean); } -// @public @deprecated (undocumented) -export type MockDirectoryOptions = CreateMockDirectoryOptions; - // @public (undocumented) export namespace mockServices { // (undocumented) @@ -475,10 +469,6 @@ export class ServiceFactoryTester< subject: ServiceFactory, options?: ServiceFactoryTesterOptions, ): ServiceFactoryTester; - // @deprecated - get( - ...args: 'root' extends TScope ? [] : [pluginId?: string] - ): Promise; getService< TGetService, TGetScope extends 'root' | 'plugin', @@ -508,13 +498,6 @@ export type ServiceMock = { : TService[Key]; }; -// @public @deprecated (undocumented) -export function setupRequestMockHandlers(worker: { - listen: (t: any) => void; - close: () => void; - resetHandlers: () => void; -}): void; - // @public (undocumented) export function startTestBackend( options: TestBackendOptions, diff --git a/packages/backend-test-utils/src/deprecated.ts b/packages/backend-test-utils/src/deprecated.ts deleted file mode 100644 index 9790580a9c..0000000000 --- a/packages/backend-test-utils/src/deprecated.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { registerMswTestHooks } from './msw'; -import { CreateMockDirectoryOptions } from './filesystem'; -import { isDockerDisabledForTests as _isDockerDisabledForTests } from './util'; - -/** - * @public - * @deprecated Use `CreateMockDirectoryOptions` from `@backstage/backend-test-utils` instead. - */ -export type MockDirectoryOptions = CreateMockDirectoryOptions; - -/** - * @public - * @deprecated Use `registerMswTestHooks` from `@backstage/backend-test-utils` instead. - */ -export function setupRequestMockHandlers(worker: { - listen: (t: any) => void; - close: () => void; - resetHandlers: () => void; -}): void { - registerMswTestHooks(worker); -} - -/** - * @public - * @deprecated This is an internal function and will no longer be exported from this package. - */ -export function isDockerDisabledForTests(): boolean { - return _isDockerDisabledForTests(); -} diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index 4124a861f4..d1d80bf387 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -20,7 +20,6 @@ * @packageDocumentation */ -export * from './deprecated'; export * from './cache'; export * from './database'; export * from './msw'; diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts index e8530f89bf..83a6a42307 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts +++ b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts @@ -82,17 +82,6 @@ export class ServiceFactoryTester< this.#registry = registry; } - /** - * Returns the service instance for the subject. - * - * @deprecated Use `getSubject` instead. - */ - async get( - ...args: 'root' extends TScope ? [] : [pluginId?: string] - ): Promise { - return this.getSubject(...args); - } - /** * Returns the service instance for the subject. * From 634a1202cd0088393553053b59f3590dec80a1af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 15:41:21 +0200 Subject: [PATCH 210/393] docs/frontend-system: initial docs for creating extension blueprints Signed-off-by: Patrik Oldsberg --- .../architecture/23-extension-blueprints.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md index 43212d4a87..ee09e7fec6 100644 --- a/docs/frontend-system/architecture/23-extension-blueprints.md +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -60,3 +60,83 @@ const myPageExtension = PageBlueprint.makeWithOverrides({ When using `makeWithOverrides`, we no longer pass the blueprint parameters directly. Instead, we provide a `factory` function that receives the original blueprint factory as the first argument, and the extension factory context as the second. We can then call the original blueprint factory with the blueprint parameters and forward the result as the return value of out factory. Notice that when passing the blueprint parameters using this pattern we have access to a lot more information than when using the `make` method, at the cost of being more complex. Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. We therefore defer to the [extension overrides](./25-extension-overrides.md) documentation for more information on how to use the `makeWithOverrides` method. + +## Creating an extension blueprint + +To create a new extension blueprint, you use the `createExtensionBlueprint` function. At the surface it is very similar to `createExtension`, but with a few key differences. Firstly you must provide a `kind` option, which will be the kind of all extensions created with the blueprint. See the [naming patterns section](./50-naming-patterns.md) for more information about how to select a good extension kind. Secondly, the `factory` function has a new signature where the first parameter is the blueprint parameters, and the second is the factory context. And finally, rather than returning an extension, `createExtensionBlueprint` returns a blueprint object with the `make` method and friends, which is used as is described above. + +The following is an example of how one might create a new extension blueprint: + +```tsx +export interface MyWidgetBlueprintParams { + defaultTitle: string; + element: JSX.Element; +} + +export const MyWidgetBlueprint = createExtensionBlueprint({ + kind: 'my-widget', + attachTo: { id: 'page:my-plugin', input: 'widgets' }, + config: { + schema: { + title: z.string().optional(), + }, + }, + output: [coreExtensionData.reactElement], + factory(params: MyWidgetBlueprintParams, { config }) { + return [ + // Note that while this is a valid pattern, you might often want to + // return separate pieces of data instead, more on that below. + coreExtensionData.reactElement( + + {params.element} + , + ), + ]; + }, +}); +``` + +This is of course a quite bare-bones example blueprint, but still a very real example. Blueprints can be very simple, there's already a lot of value in encapsulating the extension kind, attachment point, and output in a blueprint. + +Most of the options provided to `createExtensionBlueprint` can be overridden when using `makeWithOverrides` to create an extension from the blueprint. These overrides work the same way as [extension overrides](./25-extension-overrides.md), and we defer to that documentation for more information on how overrides work. + +### Blueprint-specific extension data references + +In some cases you may want to define and provide [extension data reference](./20-extensions.md#extension-data-references) that are specific to your blueprint. In the above example we might want to forward the `title` as data for example, rather than encapsulating it into the `MyWidgetContainer` component. This gives the parent extension more flexibility in the rendering for our example widget extensions. + +To do that, we create a new extension data reference for our widget title. This references is provided via the `dataRefs` options when we create the blueprint, which makes it available for use via `MyWidgetBlueprint.dataRefs.widgetTitle`. + +```tsx +export interface MyWidgetBlueprintParams { + defaultTitle: string; + element: JSX.Element; +} + +const widgetTitleRef = createExtensionDataRef().with({ + id: 'my-plugin.widget.title', +}); + +export const MyWidgetBlueprint = createExtensionBlueprint({ + kind: 'my-widget', + attachTo: { id: 'page:my-plugin', input: 'widgets' }, + config: { + schema: { + title: z.string().optional(), + }, + }, + output: [widgetTitleRef, coreExtensionData.reactElement], + factory(params: MyWidgetBlueprintParams, { config }) { + return [ + widgetTitleRef(config.title ?? params.defaultTitle), + coreExtensionData.reactElement(params.element), + ]; + }, + dataRefs: { + widgetTitle: widgetTitleRef, + }, +}); +``` + +### Extension Blueprints in libraries + +If you are publishing a plugin, the extension creators should always be exported from frontend library packages (e.g. `*-react`) rather than plugin packages. From a376559a2dcb140873846ab78138da57540207b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 14 Aug 2024 15:55:52 +0200 Subject: [PATCH 211/393] Correct the TConfig type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/curly-clouds-shave.md | 5 +++++ packages/frontend-plugin-api/api-report.md | 4 ++-- .../frontend-plugin-api/src/wiring/createExtensionDataRef.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/curly-clouds-shave.md diff --git a/.changeset/curly-clouds-shave.md b/.changeset/curly-clouds-shave.md new file mode 100644 index 0000000000..0e30f4bba1 --- /dev/null +++ b/.changeset/curly-clouds-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Correct the `TConfig` type of data references to only contain config diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index f841fc2ce2..399ecb259e 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -407,7 +407,7 @@ export interface ConfigurableExtensionDataRef< optional(): ConfigurableExtensionDataRef< TData, TId, - TData & { + TConfig & { optional: true; } >; @@ -1800,7 +1800,7 @@ export const PageBlueprint: ExtensionBlueprint< | ConfigurableExtensionDataRef< RouteRef, 'core.routing.ref', - RouteRef & { + { optional: true; } >, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index c77aa8c394..80413c1688 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -55,7 +55,7 @@ export interface ConfigurableExtensionDataRef< optional(): ConfigurableExtensionDataRef< TData, TId, - TData & { optional: true } + TConfig & { optional: true } >; (t: TData): ExtensionDataValue; } From 9b356dcaacc9192f10886d4bf254305aa56e3827 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 16:01:25 +0200 Subject: [PATCH 212/393] frontend-plugin-api: renamed createPlugin to createFrontendPlugin Signed-off-by: Patrik Oldsberg --- .changeset/chilly-numbers-chew.md | 5 +++ .../architecture/15-plugins.md | 10 ++--- .../frontend-system/architecture/36-routes.md | 12 +++--- .../architecture/50-naming-patterns.md | 4 +- .../building-apps/08-migrating.md | 2 +- .../building-plugins/01-index.md | 14 +++---- .../building-plugins/05-migrating.md | 20 +++++----- .../utility-apis/02-creating.md | 4 +- .../app-next-example-plugin/src/plugin.tsx | 4 +- .../app-next/src/examples/pagesPlugin.tsx | 4 +- .../src/collectLegacyRoutes.test.tsx | 2 +- .../src/collectLegacyRoutes.tsx | 4 +- .../compatWrapper/BackwardsCompatProvider.tsx | 2 +- .../src/routing/collectRouteIds.test.ts | 8 +++- .../extractRouteInfoFromAppNode.test.ts | 4 +- .../src/tree/createAppTree.test.ts | 4 +- .../src/tree/resolveAppNodeSpecs.test.ts | 22 +++++++---- .../src/tree/resolveAppNodeSpecs.ts | 2 +- .../src/wiring/createApp.test.tsx | 14 +++---- .../frontend-app-api/src/wiring/createApp.tsx | 2 +- .../src/wiring/discovery.test.ts | 10 ++--- packages/frontend-plugin-api/api-report.md | 39 ++++++++++--------- ...n.test.ts => createFrontendPlugin.test.ts} | 20 +++++----- ...reatePlugin.ts => createFrontendPlugin.ts} | 10 ++++- .../frontend-plugin-api/src/wiring/index.ts | 6 ++- plugins/api-docs/src/alpha.tsx | 4 +- plugins/app-visualizer/src/plugin.tsx | 4 +- plugins/catalog-graph/src/alpha.tsx | 4 +- plugins/catalog-import/src/alpha.tsx | 4 +- plugins/catalog/src/alpha/plugin.tsx | 4 +- plugins/devtools/src/alpha/plugin.tsx | 4 +- plugins/home/src/alpha.tsx | 4 +- plugins/kubernetes/src/alpha/plugin.tsx | 4 +- plugins/org/src/alpha.tsx | 4 +- plugins/scaffolder/src/alpha.tsx | 4 +- plugins/search/src/alpha.tsx | 4 +- plugins/techdocs/src/alpha.tsx | 4 +- plugins/user-settings/src/alpha.tsx | 4 +- 38 files changed, 154 insertions(+), 126 deletions(-) create mode 100644 .changeset/chilly-numbers-chew.md rename packages/frontend-plugin-api/src/wiring/{createPlugin.test.ts => createFrontendPlugin.test.ts} (95%) rename packages/frontend-plugin-api/src/wiring/{createPlugin.ts => createFrontendPlugin.ts} (95%) diff --git a/.changeset/chilly-numbers-chew.md b/.changeset/chilly-numbers-chew.md new file mode 100644 index 0000000000..3ee9e7837f --- /dev/null +++ b/.changeset/chilly-numbers-chew.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Renamed `createPlugin` to `createFrontendPlugin`. The old symbol is still exported but deprecated. diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index 042d4fbc36..c1539f0309 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -25,7 +25,7 @@ How to create a simple plugin --> ```ts -export const myPlugin = createPlugin({ +export const myPlugin = createFrontendPlugin({ id: 'my-plugin', }); ``` @@ -48,7 +48,7 @@ link to relevant docs +Each plugin is typically shipped in a separate NPM package, whether that's a published package, or just in the local workspace. The plugins instance should always the `default` export of the package, either via the main entry-point or the `/alpha` sub-path export. Each plugin package is limited to exporting a single plugin instance. In a local workspace you could use a different structure if preferred, but this is considered a non-standard layout and should be avoided in published packages. ## Creating a Plugin - +Frontend plugin instances are created with the `createFrontendPlugin` function, which is provided by the `@backstage/frontend-plugin-api` package. It takes a single options object that provides all of the necessary configuration for the plugin. In particular you will want to provide [extensions](./20-extensions.md) for your plugin, as that is the way that you can provide new functionality to the app. ```ts -export const myPlugin = createFrontendPlugin({ +// This creates a new extension, see "Extension Blueprints" documentation for more details +const myPage = PageBlueprint.make({ + params: { + defaultPath: '/my-page', + }, +}); + +export default createFrontendPlugin({ id: 'my-plugin', + extensions: [myPage], }); ``` - +The plugin ID should generally be part of the of the package name and use kebab-case. See both the [frontend naming patterns section](./50-naming-patterns.md), as well as the [package metadata section](../../tooling/package-metadata.md#name) for more information. -### Plugin ID +### `extensions` option - +### `routes` and `externalRoutes` options -### Plugin Extensions +These are the routes that the plugin exposes to the app. The `routes` option declares all of the target routes that your plugin provides, i.e. routes that other plugins and link to. The `externalRoutes` option instead declares all the outgoing routes, i.e. routes that your plugins links to, which you can bind to the `routes` of other plugins. See the [routes documentation](./36-routes.md) for more information how to set up cross-plugin navigation. - - -### Plugin Routes - - - -### Plugin External Routes - - - -### Plugin Feature Flags - - +This is a list of feature flag declarations that your plugin provides to the app. This makes sure that the feature flags are correctly registered and can be toggled in the app. To read a feature flag you can use the feature flags [Utility API](../architecture/33-utility-apis.md), accessible via `featureFlagsApiRef`. ## Installing a Plugin in an App - +A plugin instance is considered an frontend feature and can be installed directly in any Backstage frontend app. See the [app documentation](./10-app.md) for more information about the different ways in which you can install new features in an app. From c6c95a39aad3497f3a367bc3416b726e836691d6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 17:09:35 +0200 Subject: [PATCH 225/393] docs/frontend-system: add docs for how to use plugin.withOverrides Signed-off-by: Patrik Oldsberg --- .../architecture/15-plugins.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index 66fea76989..f1a24b5b4d 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -55,3 +55,36 @@ This is a list of feature flag declarations that your plugin provides to the app ## Installing a Plugin in an App A plugin instance is considered an frontend feature and can be installed directly in any Backstage frontend app. See the [app documentation](./10-app.md) for more information about the different ways in which you can install new features in an app. + +## Overriding a Plugin + +A plugin might not always behave exactly the way you want. It could be that you want to remove particular extensions, decorate them a bit, replace them with your own, or simply add new ones. Regardless of your exact use-case, you can use the `plugin.withOverrides` method to create a new copy of the plugin with the desired changes. When doing so you can also access the original extensions provided by the plugin, and use the [extension override](./25-extension-overrides.md) API to make changes to individual extensions: + +```tsx +import plugin from '@backstage/plugin-catalog'; + +export default plugin.withOverrides({ + // These overrides are merged with the original extensions + extensions: [ + // Override the catalog nav item to use a custom icon + plugin.getExtension('nav-item:catalog').override({ + factory: origFactory => [ + NavItemBlueprint.dataRefs.target({ + ...origFactory().get(NavItemBlueprint.dataRefs.target), + icon: CustomCatalogIcon, + }), + ], + }), + // Override the catalog index page with a completely custom implementation + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: plugin.routes.catalogIndex, + loader: () => import('./CustomCatalogIndexPage').then(m => ), + }, + }), + ], +}); +``` + +You can keep the plugin override in your app package, but it can often be a good idea to separate it out into its own package, especially if you the overrides are complex or you want distinct ownership of the override. For example, if you are overriding the `@backstage/plugin-catalog` plugin, you might create a new package called `@internal/plugin-catalog` at `plugins/catalog` in your workspace, which exports the overridden plugin instance. From 0e16449207f319ba03f898afa70ca7a496472f43 Mon Sep 17 00:00:00 2001 From: Brian Saltz Jr Date: Wed, 14 Aug 2024 12:20:45 -0400 Subject: [PATCH 226/393] Changing the docs for the sign-in page config from `gcpiap` to `gcpIap`. The router is case-insensitive, so they both work, but this will make everything consistent between backend and frontend. Signed-off-by: Brian Saltz Jr --- docs/auth/google/gcp-iap-auth.md | 2 +- .../src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 4976e33e33..298f212399 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -77,6 +77,6 @@ backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider')); ## Adding the provider to the Backstage frontend -See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `gcpiap` as the provider name. +See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `gcpIap` as the provider name. If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely. diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx index 0338bffbff..df0d256312 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx @@ -34,7 +34,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; */ export type ProxiedSignInPageProps = SignInPageProps & { /** - * The provider to use, e.g. "gcpiap" or "awsalb". This must correspond to + * The provider to use, e.g. "gcpIap" or "awsalb". This must correspond to * a properly configured auth provider ID in the auth backend. */ provider: string; From b77fbf4331338e0142e07c58e7015a3e2db65723 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 14 Aug 2024 11:40:19 -0500 Subject: [PATCH 227/393] Added back `type: 'local'` to TechDocs config schema Signed-off-by: Andre Wanlin --- .changeset/modern-apes-join.md | 5 +++++ plugins/techdocs-backend/config.d.ts | 25 +++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 .changeset/modern-apes-join.md diff --git a/.changeset/modern-apes-join.md b/.changeset/modern-apes-join.md new file mode 100644 index 0000000000..452896b924 --- /dev/null +++ b/.changeset/modern-apes-join.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Added back `type: 'local'` to TechDocs config schema for `publisher` diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index e22fc549d9..4910d8d76e 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -68,14 +68,20 @@ export interface Config { /** * Techdocs publisher information */ - publisher?: { - local?: { - /** - * Directory to store generated static files. - */ - publishDirectory?: string; - }; - } & ( + publisher?: + | { + type: 'local'; + + /** + * Optional when 'type' is set to local + */ + local?: { + /** + * (Optional) Directory to store generated static files. + */ + publishDirectory?: string; + }; + } | { type: 'awsS3'; @@ -258,8 +264,7 @@ export interface Config { */ projectId?: string; }; - } - ); + }; /** * @example http://localhost:7007/api/techdocs From a9a70d266272c14175fdc82b8b786fa7a42ce381 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 14 Aug 2024 18:15:00 +0200 Subject: [PATCH 228/393] fix(events,github): fix log messages for repository events Signed-off-by: Patrick Jungermann --- .../src/providers/GithubEntityProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index daade9660a..d5dd88448a 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -540,7 +540,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { const matchingTargets = this.matchesFilters([repository]); if (matchingTargets.length === 0) { this.logger.debug( - `skipping repository transferred event for repository ${repository.name} because it didn't match provider filters`, + `skipping repository renamed event for repository ${repository.name} because it didn't match provider filters`, ); return; } @@ -587,7 +587,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { const matchingTargets = this.matchesFilters([repository]); if (matchingTargets.length === 0) { this.logger.debug( - `skipping repository transferred event for repository ${repository.name} because it didn't match provider filters`, + `skipping repository unarchived event for repository ${repository.name} because it didn't match provider filters`, ); return; } From c1eb809f8b60f82bcca2ed81279e16bfd5f7b4b8 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 14 Aug 2024 18:23:30 +0200 Subject: [PATCH 229/393] fix(events,github): reliably extract org name Use `$.organization.login` instead of `$.repository.organization`. The latter is set at `push` events, but not at events of i.e. type `repository`. The replacement is available at both of them. Fixes: #25951 Signed-off-by: Patrick Jungermann --- .changeset/warm-moles-appear.md | 7 +++++++ .../src/providers/GithubEntityProvider.test.ts | 11 ++++++++--- .../src/providers/GithubEntityProvider.ts | 8 ++++---- 3 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 .changeset/warm-moles-appear.md diff --git a/.changeset/warm-moles-appear.md b/.changeset/warm-moles-appear.md new file mode 100644 index 0000000000..66ad1b9cf9 --- /dev/null +++ b/.changeset/warm-moles-appear.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Fix GitHub `repository` event support. + +`$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 002b2bbfd1..6382391610 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -702,6 +702,9 @@ describe('GithubEntityProvider', () => { const event = { ref: options?.ref ?? 'refs/heads/main', repository: repo as PushEvent['repository'], + organization: { + login: organization, + }, created: true, deleted: false, forced: false, @@ -963,7 +966,6 @@ describe('GithubEntityProvider', () => { url: 'https://github.com/test-org/test-repo', default_branch: 'main', master_branch: 'main', - organization: 'test-org', topics: [], archived: action === 'archived', private: action !== 'publicized', @@ -972,6 +974,9 @@ describe('GithubEntityProvider', () => { const event = { action, repository: repo as RepositoryEvent['repository'], + organization: { + login: 'test-org', + }, } as RepositoryEvent; if (action === 'renamed') { @@ -1285,7 +1290,7 @@ describe('GithubEntityProvider', () => { const event = createRepoEvent( 'renamed', ) as EventParams; - const urlOldRepo = `https://github.com/${event.eventPayload.repository.organization}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; + const urlOldRepo = `https://github.com/${event.eventPayload.organization?.login}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; const expectedEntitiesRemoved = createExpectedEntitiesForUrl(urlOldRepo); @@ -1314,7 +1319,7 @@ describe('GithubEntityProvider', () => { const event = createRepoEvent( 'renamed', ) as EventParams; - const urlOldRepo = `https://github.com/${event.eventPayload.repository.organization}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; + const urlOldRepo = `https://github.com/${event.eventPayload.organization?.login}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; const expectedEntitiesRemoved = createExpectedEntitiesForUrl(urlOldRepo); const expectedEntitiesAdded = createExpectedEntitiesForEvent(event); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index d5dd88448a..9e3e19d83f 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -323,9 +323,9 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onPush(event: PushEvent) { - if (this.config.organization !== event.repository.organization) { + if (this.config.organization !== event.organization?.login) { this.logger.debug( - `skipping push event from organization ${event.repository.organization}`, + `skipping push event from organization ${event.organization?.login}`, ); return; } @@ -408,9 +408,9 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onRepoChange(event: RepositoryEvent) { - if (this.config.organization !== event.repository.organization) { + if (this.config.organization !== event.organization?.login) { this.logger.debug( - `skipping repository event from organization ${event.repository.organization}`, + `skipping repository event from organization ${event.organization?.login}`, ); return; } From 9dc9d9c4d5ded50d48781a491d6769ddaa0e0636 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 14 Aug 2024 18:41:34 +0200 Subject: [PATCH 230/393] chore(events,github): reword log message Signed-off-by: Patrick Jungermann --- .../src/providers/GithubEntityProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 9e3e19d83f..f93c5b93af 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -294,7 +294,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.onEvent} */ async onEvent(params: EventParams): Promise { - this.logger.debug(`Received event from ${params.topic}`); + this.logger.debug(`Received event for topic ${params.topic}`); if (EVENT_TOPICS.some(topic => topic === params.topic)) { if (!this.connection) { throw new Error('Not initialized'); From 5152797064b62cae359e9b21de051bc39e0445d0 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 14 Aug 2024 18:49:10 +0200 Subject: [PATCH 231/393] fix(events,github): use correct repo URL `$.repository.url` was used, but contained values of pattern `https://api.github.com/repos/{org}/{repo}` for `repository` events and `https://github.com/{org}/{repo}` for `push` events. Fixes: #25951 Signed-off-by: Patrick Jungermann --- .changeset/warm-moles-appear.md | 3 ++- .../providers/GithubEntityProvider.test.ts | 6 ++++-- .../src/providers/GithubEntityProvider.ts | 21 ++++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.changeset/warm-moles-appear.md b/.changeset/warm-moles-appear.md index 66ad1b9cf9..c2809e606f 100644 --- a/.changeset/warm-moles-appear.md +++ b/.changeset/warm-moles-appear.md @@ -4,4 +4,5 @@ Fix GitHub `repository` event support. -`$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. +- `$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. +- `$.repository.url` is not always returning the expected and required value. Use `$.repository.html_url` instead. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 6382391610..79af5ad5ca 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -665,7 +665,7 @@ describe('GithubEntityProvider', () => { event: EventParams, options?: { branch?: string; catalogFilePath?: string }, ): DeferredEntity[] => { - const url = `${event.eventPayload.repository.url}/blob/${ + const url = `${event.eventPayload.repository.html_url}/blob/${ options?.branch ?? 'main' }/${options?.catalogFilePath ?? 'catalog-info.yaml'}`; return createExpectedEntitiesForUrl(url); @@ -687,6 +687,7 @@ describe('GithubEntityProvider', () => { name: 'test-repo', organization, topics: [], + html_url: `https://github.com/${organization}/test-repo`, url: `https://github.com/${organization}/test-repo`, } as Partial; @@ -963,7 +964,8 @@ describe('GithubEntityProvider', () => { ): EventParams => { const repo = { name: 'test-repo', - url: 'https://github.com/test-org/test-repo', + html_url: 'https://github.com/test-org/test-repo', + url: 'https://api.github.com/repos/test-org/test-repo', default_branch: 'main', master_branch: 'main', topics: [], diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index f93c5b93af..38008fe03c 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -331,7 +331,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } const repoName = event.repository.name; - const repoUrl = event.repository.url; + const repoUrl = event.repository.html_url; this.logger.debug(`handle github:push event for ${repoName} - ${repoUrl}`); const branch = @@ -356,13 +356,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { // so we will process the change based in this data // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push const added = this.collectDeferredEntitiesFromCommit( - event.repository.url, + repoUrl, branch, event.commits, (commit: Commit) => [...commit.added], ); const removed = this.collectDeferredEntitiesFromCommit( - event.repository.url, + repoUrl, branch, event.commits, (commit: Commit) => [...commit.removed], @@ -381,14 +381,12 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { keys: [ ...new Set([ ...modified.map( - filePath => - `url:${event.repository.url}/tree/${branch}/${filePath}`, + filePath => `url:${repoUrl}/tree/${branch}/${filePath}`, ), ...modified.map( - filePath => - `url:${event.repository.url}/blob/${branch}/${filePath}`, + filePath => `url:${repoUrl}/blob/${branch}/${filePath}`, ), - `url:${event.repository.url}/tree/${branch}/${catalogPath}`, + `url:${repoUrl}/tree/${branch}/${catalogPath}`, ]), ], }); @@ -527,7 +525,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { private async onRepoRenamed(event: RepositoryRenamedEvent) { const repository = this.createRepoFromEvent(event); const oldRepoName = event.changes.repository.name.from; - const urlParts = event.repository.url.split('/'); + const urlParts = repository.url.split('/'); urlParts[urlParts.length - 1] = oldRepoName; const oldRepoUrl = urlParts.join('/'); const oldRepository: Repository = { @@ -632,7 +630,10 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { private createRepoFromEvent(event: RepositoryEvent | PushEvent): Repository { return { - url: event.repository.url, + // $.repository.url can be a value like + // "https://api.github.com/repos/{org}/{repo}" + // or "https://github.com/{org}/{repo}" + url: event.repository.html_url, name: event.repository.name, defaultBranchRef: event.repository.default_branch, repositoryTopics: event.repository.topics, From 55f994321b556b340c7f3c828e5ee3d8cbff6845 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Aug 2024 21:22:16 +0200 Subject: [PATCH 232/393] docs/frontend-system: add initial core migration docs Signed-off-by: Patrik Oldsberg --- .../architecture/60-migrations.md | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/frontend-system/architecture/60-migrations.md diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md new file mode 100644 index 0000000000..6ba3c73f0e --- /dev/null +++ b/docs/frontend-system/architecture/60-migrations.md @@ -0,0 +1,74 @@ +--- +id: migrations +title: Frontend System Migrations +sidebar_label: Migrations +# prettier-ignore +description: Migration documentation for different versions of the frontend system core APIs. +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +## Introduction + +This section provides migration guides for different versions of the frontend system core APIs. Each guide will provide a summary of the changes that have been made for a particular Backstage release, and how to update your usage of the core APIs. + +This guide is intended for app and plugin authors who have already migrated their code to the new frontend system, and are looking to keep it up to date with the latest changes. These guides do not cover trivial migrations that can be explained in a deprecation message, such as a renamed export. + +## 1.30 + +### Reworked extension inputs and outputs + +In previous versions of the frontend system you would define extension inputs and outputs as an "data map" where each named property corresponded to a piece of data that could be accessed via the same name in the extension factory. In order to better support extension overrides and blueprints, as well as reduce the risk of confusion, these data maps have been replaced by an array of data references. + +For example, an extension previously declared like this: + +```tsx +createExtension({ + name: 'example', + attachTo: { id: 'some-extension', input: 'content' }, + inputs: { + header: createExtensionInput( + { element: coreExtensionData.reactElement }, + { + optional: true, + singleton: true, + }, + ), + }, + output: { element: coreExtensionData.reactElement }, + factory({ inputs }) { + return { + element: , + }; + }, +}); +``` + +Would now look like this: + +```tsx +createExtension({ + name: 'example', + attachTo: { id: 'some-extension', input: 'content' }, + inputs: { + header: createExtensionInput([coreExtensionData.reactElement], { + optional: true, + singleton: true, + }), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( + , + ), + ]; + }, +}); +``` + +Note the changes to the `inputs` and `output` declarations, as well as how these are used in the factory. Both the `inputs` and `output` now declare their expected data using an array, without names tied to each piece of data. The `get` method on the input object is used to access the input data, and accept a data reference matching the data that was declared for that input. + +The biggest change in practice is how the extension factories output their data. Instead of returning an object with the data values, you instead use each extension data reference to encapsulate a value and return a collection of these encapsulated values from the factory. From dcd6a7923a08c91eeeb8eda63b0c68033a15486c Mon Sep 17 00:00:00 2001 From: "Jonas M. Hansen" Date: Thu, 15 Aug 2024 08:10:29 +0200 Subject: [PATCH 233/393] Added OpenTelemetry support to Scaffolder metrics Signed-off-by: Jonas M. Hansen --- .changeset/large-monkeys-dress.md | 5 + plugins/scaffolder-backend/package.json | 1 + .../tasks/NunjucksWorkflowRunner.ts | 92 ++++++++++++++++--- yarn.lock | 1 + 4 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 .changeset/large-monkeys-dress.md diff --git a/.changeset/large-monkeys-dress.md b/.changeset/large-monkeys-dress.md new file mode 100644 index 0000000000..369a3994c5 --- /dev/null +++ b/.changeset/large-monkeys-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Added OpenTelemetry support to Scaffolder metrics diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f3d10ba478..d9023f3711 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -86,6 +86,7 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", + "@opentelemetry/api": "^1.3.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "concat-stream": "^2.0.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 3b88fd0f10..3d7d8b8b1d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -26,6 +26,7 @@ import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; import { TemplateActionRegistry } from '../actions'; +import { metrics } from '@opentelemetry/api'; import { SecureTemplater, SecureTemplateRenderer, @@ -520,36 +521,62 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } function scaffoldingTracker() { - const taskCount = createCounterMetric({ + // prom-client metrics are deprecated in favour of OpenTelemetry metrics. + const promTaskCount = createCounterMetric({ name: 'scaffolder_task_count', help: 'Count of task runs', labelNames: ['template', 'user', 'result'], }); - const taskDuration = createHistogramMetric({ + const promTaskDuration = createHistogramMetric({ name: 'scaffolder_task_duration', help: 'Duration of a task run', labelNames: ['template', 'result'], }); - const stepCount = createCounterMetric({ + const promtStepCount = createCounterMetric({ name: 'scaffolder_step_count', help: 'Count of step runs', labelNames: ['template', 'step', 'result'], }); - const stepDuration = createHistogramMetric({ + const promStepDuration = createHistogramMetric({ name: 'scaffolder_step_duration', help: 'Duration of a step runs', labelNames: ['template', 'step', 'result'], }); + const meter = metrics.getMeter('default'); + const taskCount = meter.createCounter('scaffolder.task.count', { + description: 'Count of task runs', + }); + + const taskDuration = meter.createHistogram('scaffolder.task.duration', { + description: 'Duration of a task run', + unit: 'seconds', + }); + + const stepCount = meter.createCounter('scaffolder.step.count', { + description: 'Count of step runs', + }); + + const stepDuration = meter.createHistogram('scaffolder.step.duration', { + description: 'Duration of a step runs', + unit: 'seconds', + }); + async function taskStart(task: TaskContext) { await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`); const template = task.spec.templateInfo?.entityRef || ''; const user = task.spec.user?.ref || ''; - const taskTimer = taskDuration.startTimer({ + const startTime = process.hrtime(); + const taskTimer = promTaskDuration.startTimer({ template, }); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } + async function skipDryRun( step: TaskStep, action: TemplateAction, @@ -561,12 +588,17 @@ function scaffoldingTracker() { } async function markSuccessful() { - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'ok', }); taskTimer({ result: 'ok' }); + + taskCount.add(1, { template, user, result: 'ok' }); + taskDuration.record(endTime(), { + result: 'ok', + }); } async function markFailed(step: TaskStep, err: Error) { @@ -574,12 +606,17 @@ function scaffoldingTracker() { stepId: step.id, status: 'failed', }); - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'failed', }); taskTimer({ result: 'failed' }); + + taskCount.add(1, { template, user, result: 'failed' }); + taskDuration.record(endTime(), { + result: 'failed', + }); } async function markCancelled(step: TaskStep) { @@ -587,12 +624,17 @@ function scaffoldingTracker() { stepId: step.id, status: 'cancelled', }); - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'cancelled', }); taskTimer({ result: 'cancelled' }); + + taskCount.add(1, { template, user, result: 'cancelled' }); + taskDuration.record(endTime(), { + result: 'cancelled', + }); } return { @@ -610,40 +652,61 @@ function scaffoldingTracker() { }); const template = task.spec.templateInfo?.entityRef || ''; - const stepTimer = stepDuration.startTimer({ + const startTime = process.hrtime(); + const stepTimer = promStepDuration.startTimer({ template, step: step.name, }); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } + async function markSuccessful() { await task.emitLog(`Finished step ${step.name}`, { stepId: step.id, status: 'completed', }); - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'ok', }); stepTimer({ result: 'ok' }); + + stepCount.add(1, { template, step: step.name, result: 'ok' }); + stepDuration.record(endTime(), { + result: 'ok', + }); } async function markCancelled() { - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'cancelled', }); stepTimer({ result: 'cancelled' }); + + stepCount.add(1, { template, step: step.name, result: 'cancelled' }); + stepDuration.record(endTime(), { + result: 'cancelled', + }); } async function markFailed() { - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'failed', }); stepTimer({ result: 'failed' }); + + stepCount.add(1, { template, step: step.name, result: 'failed' }); + stepDuration.record(endTime(), { + result: 'failed', + }); } async function skipFalsy() { @@ -652,6 +715,11 @@ function scaffoldingTracker() { { stepId: step.id, status: 'skipped' }, ); stepTimer({ result: 'skipped' }); + + stepCount.add(1, { template, step: step.name, result: 'skipped' }); + stepDuration.record(endTime(), { + result: 'skipped', + }); } return { diff --git a/yarn.lock b/yarn.lock index 9c292f5903..3d5f6c4cb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7205,6 +7205,7 @@ __metadata: "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@opentelemetry/api": ^1.3.0 "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 "@types/luxon": ^3.0.0 From b58e452caf72b3e994aa18854681518428ff12b4 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 14 Aug 2024 14:52:43 +0200 Subject: [PATCH 234/393] Mark broadcast notifications in the list Signed-off-by: Marek Libra --- .changeset/healthy-islands-smash.md | 5 ++ .../NotificationsTable/NotificationsTable.tsx | 51 ++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 .changeset/healthy-islands-smash.md diff --git a/.changeset/healthy-islands-smash.md b/.changeset/healthy-islands-smash.md new file mode 100644 index 0000000000..782a88167c --- /dev/null +++ b/.changeset/healthy-islands-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Broadcast notifications are now decorated with an icon. diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index d7c4c40781..8a6cbd17d7 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -24,6 +24,7 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { Notification } from '@backstage/plugin-notifications-common'; import { useConfirm } from 'material-ui-confirm'; +import BroadcastIcon from '@material-ui/icons/RssFeed'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { Link, @@ -40,7 +41,7 @@ import { BulkActions } from './BulkActions'; const ThrottleDelayMs = 1000; -const useStyles = makeStyles({ +const useStyles = makeStyles(theme => ({ description: { maxHeight: '5rem', overflow: 'auto', @@ -48,7 +49,15 @@ const useStyles = makeStyles({ severityItem: { alignContent: 'center', }, -}); + broadcastIcon: { + fontSize: '1rem', + verticalAlign: 'text-bottom', + }, + notificationInfoRow: { + marginLeft: theme.spacing(0.5), + marginRight: theme.spacing(0.5), + }, +})); /** @public */ export type NotificationsTableProps = Pick< @@ -236,15 +245,40 @@ export const NotificationsTable = ({ {notification.payload.description} ) : null} + + {!notification.user && ( + <> + + + )} {notification.origin && ( - <>{notification.origin} •  + <> + + {notification.origin} + + • + )} {notification.payload.topic && ( - <>{notification.payload.topic} •  + <> + + {notification.payload.topic} + + • + )} {notification.created && ( - + )} @@ -272,15 +306,13 @@ export const NotificationsTable = ({ selectedNotifications={new Set([notification.id])} onSwitchReadStatus={onSwitchReadStatus} onSwitchSavedStatus={onSwitchSavedStatus} - // /> ), }, ]; }, [ - markAsReadOnLinkOpen, - selectedNotifications, notifications, + selectedNotifications, isUnread, onSwitchReadStatus, onSwitchSavedStatus, @@ -288,6 +320,9 @@ export const NotificationsTable = ({ onNotificationsSelectChange, classes.severityItem, classes.description, + classes.broadcastIcon, + classes.notificationInfoRow, + markAsReadOnLinkOpen, ]); return ( From 1129e946b821eae77ccff23f71248c7860d5e349 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 13:06:32 +0200 Subject: [PATCH 235/393] chore: migrate some of the catalog plugin to blueprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: blam --- plugins/catalog/package.json | 1 + .../CatalogFilterBlueprint.test.tsx | 102 +++++++++++++ .../blueprints/CatalogFilterBlueprint.tsx | 44 ++++++ .../{alpha.ts => alpha/blueprints/index.ts} | 8 +- .../alpha/createCatalogFilterExtension.tsx | 5 +- plugins/catalog/src/alpha/entityCards.tsx | 140 ++++++++++-------- plugins/catalog/src/alpha/entityContents.tsx | 53 ++++--- plugins/catalog/src/alpha/index.ts | 3 + .../catalog/src/{ => alpha}/translation.ts | 0 .../src/components/AboutCard/AboutCard.tsx | 2 +- .../src/components/AboutCard/AboutContent.tsx | 2 +- .../CatalogPage/DefaultCatalogPage.tsx | 2 +- .../CatalogSearchResultListItem.tsx | 2 +- .../components/CatalogTable/CatalogTable.tsx | 2 +- .../DependencyOfComponentsCard.tsx | 2 +- .../DependsOnComponentsCard.tsx | 2 +- .../DependsOnResourcesCard.tsx | 2 +- .../EntityContextMenu/EntityContextMenu.tsx | 2 +- .../EntityContextMenu/UnregisterEntity.tsx | 2 +- .../EntityLabelsCard/EntityLabelsCard.tsx | 2 +- .../EntityLabelsEmptyState.tsx | 2 +- .../components/EntityLayout/EntityLayout.tsx | 2 +- .../EntityLinksCard/EntityLinksCard.tsx | 2 +- .../EntityLinksCard/EntityLinksEmptyState.tsx | 2 +- .../EntityNotFound/EntityNotFound.tsx | 2 +- .../DeleteEntityDialog.tsx | 2 +- .../EntityOrphanWarning.tsx | 2 +- .../EntityProcessingErrorsPanel.tsx | 2 +- .../EntityRelationWarning.tsx | 2 +- .../HasComponentsCard/HasComponentsCard.tsx | 2 +- .../HasResourcesCard/HasResourcesCard.tsx | 2 +- .../HasSubcomponentsCard.tsx | 2 +- .../HasSubdomainsCard/HasSubdomainsCard.tsx | 2 +- .../HasSystemsCard/HasSystemsCard.tsx | 2 +- .../RelatedEntitiesCard.tsx | 2 +- .../SystemDiagramCard/SystemDiagramCard.tsx | 2 +- 36 files changed, 295 insertions(+), 115 deletions(-) create mode 100644 plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx create mode 100644 plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx rename plugins/catalog/src/{alpha.ts => alpha/blueprints/index.ts} (73%) rename plugins/catalog/src/{ => alpha}/translation.ts (100%) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index aaef0ab951..2f6d3cf65c 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -90,6 +90,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", diff --git a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx new file mode 100644 index 0000000000..de8d037adc --- /dev/null +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { CatalogFilterBlueprint } from './CatalogFilterBlueprint'; +import { + coreExtensionData, + createExtension, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { waitFor, screen } from '@testing-library/react'; + +describe('CatalogFilterBlueprint', () => { + it('should create an extension with sane defaults', () => { + const extension = CatalogFilterBlueprint.make({ + params: { + loader: async () =>
, + }, + }); + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "page:catalog", + "input": "filters", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "catalog-filter", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should allow overrding of inputs and config', async () => { + const extension = CatalogFilterBlueprint.makeWithOverrides({ + name: 'test-name', + inputs: { + mock: createExtensionInput([coreExtensionData.reactElement]), + }, + config: { + schema: { + test: z => z.string(), + }, + }, + factory(originalFactory, { config, inputs }) { + return originalFactory({ + loader: async () => ( +
+ config: {config.test} +
+ {inputs.mock.map(i => i.get(coreExtensionData.reactElement))} +
+
+ ), + }); + }, + }); + + const mockExtension = createExtension({ + attachTo: { id: 'catalog-filter:test-name', input: 'mock' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
im a mock
)]; + }, + }); + + createExtensionTester(extension, { config: { test: 'mock test config' } }) + .add(mockExtension) + .render(); + + await waitFor(() => { + expect(screen.getByTestId('test')).toBeInTheDocument(); + expect(screen.getByTestId('test')).toHaveTextContent( + 'config: mock test config', + ); + expect(screen.getByTestId('contents')).toHaveTextContent('im a mock'); + }); + }); +}); diff --git a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx new file mode 100644 index 0000000000..e8d6df55d2 --- /dev/null +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { lazy } from 'react'; +import { + ExtensionBoundary, + coreExtensionData, + createExtensionBlueprint, +} from '@backstage/frontend-plugin-api'; + +/** + * Creates Catalog Filter Extensions + * @alpha + */ +export const CatalogFilterBlueprint = createExtensionBlueprint({ + kind: 'catalog-filter', + attachTo: { id: 'page:catalog', input: 'filters' }, + output: [coreExtensionData.reactElement], + factory(params: { loader: () => Promise }, { node }) { + const ExtensionComponent = lazy(() => + params.loader().then(element => ({ default: () => element })), + ); + return [ + coreExtensionData.reactElement( + + + , + ), + ]; + }, +}); diff --git a/plugins/catalog/src/alpha.ts b/plugins/catalog/src/alpha/blueprints/index.ts similarity index 73% rename from plugins/catalog/src/alpha.ts rename to plugins/catalog/src/alpha/blueprints/index.ts index b78a5e6225..a3a7bcb33f 100644 --- a/plugins/catalog/src/alpha.ts +++ b/plugins/catalog/src/alpha/blueprints/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export * from './alpha/index'; -export { default } from './alpha/index'; -export { catalogTranslationRef } from './translation'; -export * from './translation'; +export { CatalogFilterBlueprint } from './CatalogFilterBlueprint'; diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx index d8a218f8f3..eb6d58f424 100644 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx @@ -23,7 +23,10 @@ import { createExtension, } from '@backstage/frontend-plugin-api'; -/** @alpha */ +/** + * @alpha + * @deprecated Use {@link CatalogFilterExtensionBlueprint} instead + */ export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, TConfig, diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index 8a18efabde..5f8ee16f87 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -15,96 +15,116 @@ */ import React from 'react'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { compatWrapper } from '@backstage/core-compat-api'; -export const catalogAboutEntityCard = createEntityCardExtension({ +export const catalogAboutEntityCard = EntityCardBlueprint.make({ name: 'about', - loader: async () => - import('../components/AboutCard').then(m => - compatWrapper(), - ), + params: { + loader: async () => + import('../components/AboutCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogLinksEntityCard = createEntityCardExtension({ +export const catalogLinksEntityCard = EntityCardBlueprint.make({ name: 'links', - filter: 'has:links', - loader: async () => - import('../components/EntityLinksCard').then(m => - compatWrapper(), - ), + params: { + filter: 'has:links', + loader: async () => + import('../components/EntityLinksCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogLabelsEntityCard = createEntityCardExtension({ +export const catalogLabelsEntityCard = EntityCardBlueprint.make({ name: 'labels', - filter: 'has:labels', - loader: async () => - import('../components/EntityLabelsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'has:labels', + loader: async () => + import('../components/EntityLabelsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogDependsOnComponentsEntityCard = createEntityCardExtension({ +export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({ name: 'depends-on-components', - filter: 'kind:component', - loader: async () => - import('../components/DependsOnComponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/DependsOnComponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogDependsOnResourcesEntityCard = createEntityCardExtension({ +export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({ name: 'depends-on-resources', - filter: 'kind:component', - loader: async () => - import('../components/DependsOnResourcesCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/DependsOnResourcesCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasComponentsEntityCard = createEntityCardExtension({ +export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({ name: 'has-components', - filter: 'kind:system', - loader: async () => - import('../components/HasComponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:system', + loader: async () => + import('../components/HasComponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasResourcesEntityCard = createEntityCardExtension({ +export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({ name: 'has-resources', - filter: 'kind:system', - loader: async () => - import('../components/HasResourcesCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:system', + loader: async () => + import('../components/HasResourcesCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSubcomponentsEntityCard = createEntityCardExtension({ +export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({ name: 'has-subcomponents', - filter: 'kind:component', - loader: async () => - import('../components/HasSubcomponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/HasSubcomponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSubdomainsEntityCard = createEntityCardExtension({ +export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ name: 'has-subdomains', - filter: 'kind:domain', - loader: async () => - import('../components/HasSubdomainsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:domain', + loader: async () => + import('../components/HasSubdomainsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSystemsEntityCard = createEntityCardExtension({ +export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({ name: 'has-systems', - filter: 'kind:domain', - loader: async () => - import('../components/HasSystemsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:domain', + loader: async () => + import('../components/HasSystemsCard').then(m => + compatWrapper(), + ), + }, }); export default [ diff --git a/plugins/catalog/src/alpha/entityContents.tsx b/plugins/catalog/src/alpha/entityContents.tsx index 75c3fef49f..259b92492c 100644 --- a/plugins/catalog/src/alpha/entityContents.tsx +++ b/plugins/catalog/src/alpha/entityContents.tsx @@ -19,27 +19,38 @@ import { coreExtensionData, createExtensionInput, } from '@backstage/frontend-plugin-api'; -import { - createEntityContentExtension, - catalogExtensionData, -} from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; -export const catalogOverviewEntityContent = createEntityContentExtension({ - name: 'overview', - defaultPath: '/', - defaultTitle: 'Overview', - disabled: false, - inputs: { - cards: createExtensionInput({ - element: coreExtensionData.reactElement, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }), - }, - loader: async ({ inputs }) => - import('./EntityOverviewPage').then(m => ( - c.output)} /> - )), -}); +export const catalogOverviewEntityContent = + EntityContentBlueprint.makeWithOverrides({ + name: 'overview', + inputs: { + cards: createExtensionInput([ + coreExtensionData.reactElement, + EntityContentBlueprint.dataRefs.filterFunction.optional(), + EntityContentBlueprint.dataRefs.filterExpression.optional(), + ]), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + defaultPath: '/', + defaultTitle: 'Overview', + loader: async () => + import('./EntityOverviewPage').then(m => ( + ({ + element: c.get(coreExtensionData.reactElement), + filterFunction: c.get( + EntityContentBlueprint.dataRefs.filterFunction, + ), + filterExpression: c.get( + EntityContentBlueprint.dataRefs.filterExpression, + ), + }))} + /> + )), + }); + }, + }); export default [catalogOverviewEntityContent]; diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index 06a78ec6ea..f004c4f19e 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -16,3 +16,6 @@ export { default } from './plugin'; export { createCatalogFilterExtension } from './createCatalogFilterExtension'; + +export * from './blueprints'; +export * from './translation'; diff --git a/plugins/catalog/src/translation.ts b/plugins/catalog/src/alpha/translation.ts similarity index 100% rename from plugins/catalog/src/translation.ts rename to plugins/catalog/src/alpha/translation.ts diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index ff5d7fc801..cf4a3c2d43 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -64,7 +64,7 @@ import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common import { useSourceTemplateCompoundEntityRef } from './hooks'; import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { usePermission } from '@backstage/plugin-permission-react'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 3f781f4759..f6d2d80264 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -33,7 +33,7 @@ import React from 'react'; import { AboutField } from './AboutField'; import { LinksGridList } from '../EntityLinksCard/LinksGridList'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; const useStyles = makeStyles({ description: { diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index db765b9145..a46a552b8d 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -33,7 +33,7 @@ import { import React, { ReactNode } from 'react'; import { createComponentRouteRef } from '../../routes'; import { CatalogTable, CatalogTableRow } from '../CatalogTable'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogTableColumnsFunc } from '../CatalogTable/types'; diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index 1a596f8b9b..f4501bd795 100644 --- a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -27,7 +27,7 @@ import { ResultHighlight, } from '@backstage/plugin-search-common'; import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; const useStyles = makeStyles( diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index e18467249a..1026dc5d26 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -49,7 +49,7 @@ import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; import { PaginatedCatalogTable } from './PaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; /** * Props for {@link CatalogTable}. diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx index 2f0557d11e..6c6f811cc1 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx @@ -30,7 +30,7 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx index fe56ea582b..eafa33346d 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx @@ -27,7 +27,7 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx index 281bfd8766..723b87cc29 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx @@ -27,7 +27,7 @@ import { RelatedEntitiesCard, resourceEntityColumns, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index 5b740038a1..77f934e7de 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -33,7 +33,7 @@ import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common/ import { UnregisterEntity, UnregisterEntityOptions } from './UnregisterEntity'; import { useApi, alertApiRef } from '@backstage/core-plugin-api'; import useCopyToClipboard from 'react-use/esm/useCopyToClipboard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx index 08496d4819..63247d0c62 100644 --- a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx @@ -19,7 +19,7 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import MenuItem from '@material-ui/core/MenuItem'; import CancelIcon from '@material-ui/icons/Cancel'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type VisibleType = 'visible' | 'hidden' | 'disable'; diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index d48b45f054..c4557b6774 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -25,7 +25,7 @@ import { import { EntityLabelsEmptyState } from './EntityLabelsEmptyState'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx index 16d0a3a897..d78be2d2ce 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx @@ -19,7 +19,7 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { CodeSnippet } from '@backstage/core-components'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const ENTITY_YAML = `metadata: diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index bbc7792acb..e8b957497d 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -53,7 +53,7 @@ import React, { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; import { rootRouteRef, unregisterRedirectRouteRef } from '../../routes'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index b5d8269a0b..59e1f6d252 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -23,7 +23,7 @@ import { ColumnBreakpoints } from './types'; import { IconComponent, useApp } from '@backstage/core-plugin-api'; import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; /** @public */ export interface EntityLinksCardProps { diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx index 09362385f9..513183677d 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx @@ -20,7 +20,7 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { CodeSnippet } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; const ENTITY_YAML = `metadata: name: example diff --git a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx index 252005dff5..a033199ffd 100644 --- a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx +++ b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx @@ -20,7 +20,7 @@ import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { Illo } from './Illo'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles(theme => ({ diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index c9c24a14a1..b1a7afbb50 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -23,7 +23,7 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import React, { useState } from 'react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { assertError } from '@backstage/errors'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; interface DeleteEntityDialogProps { diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx index 10a10e54fd..eb561f9c19 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx @@ -22,7 +22,7 @@ import { useNavigate } from 'react-router-dom'; import { DeleteEntityDialog } from './DeleteEntityDialog'; import { useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** diff --git a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx index bd49785abe..3c7c5bb2e3 100644 --- a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx +++ b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx @@ -31,7 +31,7 @@ import { import { useApi, ApiHolder } from '@backstage/core-plugin-api'; import useAsync from 'react-use/esm/useAsync'; import { SerializedError } from '@backstage/errors'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const errorFilter = (i: EntityStatusItem) => diff --git a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx index 2ce9120bf0..ed816bfc75 100644 --- a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx +++ b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx @@ -26,7 +26,7 @@ import useAsync from 'react-use/esm/useAsync'; import Box from '@material-ui/core/Box'; import { ResponseErrorPanel } from '@backstage/core-components'; import { useApi, ApiHolder } from '@backstage/core-plugin-api'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; async function getRelationWarnings(entity: Entity, catalogApi: CatalogApi) { diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index 13a9e75928..2fc6cde81b 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -27,7 +27,7 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index b99bca1716..6b90ce0443 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -27,7 +27,7 @@ import { resourceEntityColumns, resourceEntityHelpLink, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index ceea839ef4..ceab037a45 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -26,7 +26,7 @@ import { componentEntityColumns, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx index 811f74d820..71705ea90f 100644 --- a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx +++ b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx @@ -22,7 +22,7 @@ import { componentEntityColumns, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index 15b4f933ba..48742f9d28 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -27,7 +27,7 @@ import { systemEntityColumns, systemEntityHelpLink, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx index e6e375646d..a2814527cc 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx +++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx @@ -42,7 +42,7 @@ import { systemEntityColumns, systemEntityHelpLink, } from './presets'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx index 5e485d672b..879a5076b1 100644 --- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx +++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx @@ -46,7 +46,7 @@ import { } from '@backstage/core-components'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ From 52a2e398020e1cf658bad6f6294b4263a1cee1e4 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 13:08:01 +0200 Subject: [PATCH 236/393] chore: fix package.json export Signed-off-by: blam --- plugins/catalog/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2f6d3cf65c..b8f8864f3c 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -29,7 +29,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -37,7 +37,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.ts" + "src/alpha/index.ts" ], "package.json": [ "package.json" From 7999f618077a05b92e1715d44b5516922bfcb81e Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 13:50:25 +0200 Subject: [PATCH 237/393] chore: tidy up the last of the catalog tests Signed-off-by: blam --- .../blueprints/CatalogFilterBlueprint.test.tsx | 4 +++- plugins/catalog/src/setupTests.ts | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx index de8d037adc..9cf8b65957 100644 --- a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx @@ -71,7 +71,9 @@ describe('CatalogFilterBlueprint', () => {
config: {config.test}
- {inputs.mock.map(i => i.get(coreExtensionData.reactElement))} + {inputs.mock.map((i, k) => ( +
{i.get(coreExtensionData.reactElement)}
+ ))}
), diff --git a/plugins/catalog/src/setupTests.ts b/plugins/catalog/src/setupTests.ts index 963c0f188b..dae4360fd3 100644 --- a/plugins/catalog/src/setupTests.ts +++ b/plugins/catalog/src/setupTests.ts @@ -15,3 +15,18 @@ */ import '@testing-library/jest-dom'; + +// eslint-disable-next-line no-console +const originalConsoleWarn = console.warn; +// eslint-disable-next-line no-console +console.warn = (...args: any[]) => { + const message = args[0]; + if ( + typeof message === 'string' && + (message.includes('CSSOM.parse is not a function') || + message.includes('[JSS]')) + ) { + return; + } + originalConsoleWarn(...args); +}; From 1612837c8ebe678f4b3825151c55794dca1d2a05 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 13:50:46 +0200 Subject: [PATCH 238/393] chore: updating catalog react Signed-off-by: blam --- plugins/catalog-react/package.json | 5 +- .../blueprints/EntityCardBlueprint.test.tsx | 178 ++++++++++++++ .../alpha/blueprints/EntityCardBlueprint.tsx | 75 ++++++ .../EntityContentBlueprint.test.tsx | 226 ++++++++++++++++++ .../blueprints/EntityContentBlueprint.tsx | 99 ++++++++ .../src/alpha/blueprints/index.ts | 17 ++ .../src/{alpha.tsx => alpha/extensions.tsx} | 23 +- plugins/catalog-react/src/alpha/index.ts | 17 ++ plugins/catalog-react/src/setupTests.ts | 15 ++ 9 files changed, 646 insertions(+), 9 deletions(-) create mode 100644 plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx create mode 100644 plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx create mode 100644 plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx create mode 100644 plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx create mode 100644 plugins/catalog-react/src/alpha/blueprints/index.ts rename plugins/catalog-react/src/{alpha.tsx => alpha/extensions.tsx} (93%) create mode 100644 plugins/catalog-react/src/alpha/index.ts diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index e6b6acae3a..ef5d924200 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -29,7 +29,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.tsx", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -37,7 +37,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.tsx" + "src/alpha/index.ts" ], "package.json": [ "package.json" @@ -85,6 +85,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/test-utils": "workspace:^", diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx new file mode 100644 index 0000000000..db787766fd --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx @@ -0,0 +1,178 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { EntityCardBlueprint } from './EntityCardBlueprint'; +import { + coreExtensionData, + createExtension, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { waitFor, screen } from '@testing-library/react'; +import { Entity } from '@backstage/catalog-model'; + +describe('EntityCardBlueprint', () => { + it('should return an extension with sensible defaults', () => { + const extension = EntityCardBlueprint.make({ + name: 'test', + params: { + filter: 'has:labels', + loader: async () =>
im a card
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "entity-content:catalog/overview", + "input": "cards", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "filter": { + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "entity-card", + "name": "test", + "namespace": undefined, + "output": [ + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-function", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-expression", + "optional": [Function], + "toString": [Function], + }, + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should output the correct filter output', () => { + const mockFilter = (_entity: Entity) => true; + + expect( + createExtensionTester( + EntityCardBlueprint.make({ + name: 'test', + params: { + loader: async () =>
Test!
, + filter: 'test', + }, + }), + ).data(EntityCardBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityCardBlueprint.make({ + name: 'test', + params: { + loader: async () =>
Test!
, + }, + }), + { config: { filter: 'test' } }, + ).data(EntityCardBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityCardBlueprint.make({ + name: 'test', + params: { + filter: mockFilter, + loader: async () =>
Test!
, + }, + }), + ).data(EntityCardBlueprint.dataRefs.filterFunction), + ).toBe(mockFilter); + }); + + it('should allow overriding config and inputs', async () => { + const extension = EntityCardBlueprint.makeWithOverrides({ + name: 'test', + inputs: { + mock: createExtensionInput([coreExtensionData.reactElement]), + }, + config: { + schema: { + mock: z => z.string(), + }, + }, + factory(originalFactory, { inputs, config }) { + return originalFactory({ + loader: async () => ( +
+ config: {config.mock} +
+ {inputs.mock.map((i, k) => ( +
{i.get(coreExtensionData.reactElement)}
+ ))} +
+
+ ), + }); + }, + }); + + const mockExtension = createExtension({ + attachTo: { id: 'entity-card:test', input: 'mock' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
im a mock
)]; + }, + }); + + createExtensionTester(extension, { config: { mock: 'mock test config' } }) + .add(mockExtension) + .render(); + + await waitFor(() => { + expect(screen.getByTestId('test')).toBeInTheDocument(); + expect(screen.getByTestId('test')).toHaveTextContent( + 'config: mock test config', + ); + expect(screen.getByTestId('contents')).toHaveTextContent('im a mock'); + }); + }); +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx new file mode 100644 index 0000000000..c3153b6059 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { lazy } from 'react'; +import { + ExtensionBoundary, + coreExtensionData, + createExtensionBlueprint, +} from '@backstage/frontend-plugin-api'; +import { catalogExtensionData } from '../extensions'; + +/** + * @alpha + * A blueprint for creating cards for the entity pages in the catalog. + */ +export const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'entity-content:catalog/overview', input: 'cards' }, + output: [ + coreExtensionData.reactElement, + catalogExtensionData.entityFilterFunction.optional(), + catalogExtensionData.entityFilterExpression.optional(), + ], + dataRefs: { + filterFunction: catalogExtensionData.entityFilterFunction, + filterExpression: catalogExtensionData.entityFilterExpression, + }, + config: { + schema: { + filter: z => z.string().optional(), + }, + }, + *factory( + { + loader, + filter, + }: { + loader: () => Promise; + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; + }, + { node, config }, + ) { + const ExtensionComponent = lazy(() => + loader().then(element => ({ default: () => element })), + ); + + yield coreExtensionData.reactElement( + + + , + ); + + if (config.filter) { + yield catalogExtensionData.entityFilterExpression(config.filter); + } else if (typeof filter === 'string') { + yield catalogExtensionData.entityFilterExpression(filter); + } else if (typeof filter === 'function') { + yield catalogExtensionData.entityFilterFunction(filter); + } + }, +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx new file mode 100644 index 0000000000..dab6013bab --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -0,0 +1,226 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { EntityContentBlueprint } from './EntityContentBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + coreExtensionData, + createExtension, + createExtensionInput, + createRouteRef, +} from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { waitFor, screen } from '@testing-library/react'; + +describe('EntityContentBlueprint', () => { + it('should return an extension with sane defaults', () => { + const extension = EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () =>
Test!
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "page:catalog/entity", + "input": "contents", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "filter": { + "type": "string", + }, + "path": { + "type": "string", + }, + "title": { + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "entity-content", + "name": "test", + "namespace": undefined, + "output": [ + [Function], + [Function], + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.routing.ref", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-function", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-expression", + "optional": [Function], + "toString": [Function], + }, + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should emit the correct defaults', () => { + const mockRouteRef = createRouteRef(); + const extension = EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + routeRef: mockRouteRef, + loader: async () =>
Test!
, + }, + }); + + const tester = createExtensionTester(extension); + + // todo(blam): route paths are always set to / in the createExtensionTester. This will work eventually. + // expect(tester.data(coreExtensionData.routePath)).toBe('/test'); + + expect(tester.data(coreExtensionData.routeRef)).toBe(mockRouteRef); + expect(tester.data(EntityContentBlueprint.dataRefs.title)).toBe('Test'); + }); + + it('should emit the correct filter output', () => { + const mockFilter = (_entity: Entity) => true; + + expect( + createExtensionTester( + EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () =>
Test!
, + filter: 'test', + }, + }), + ).data(EntityContentBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () =>
Test!
, + }, + }), + { config: { filter: 'test' } }, + ).data(EntityContentBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + filter: mockFilter, + loader: async () =>
Test!
, + }, + }), + ).data(EntityContentBlueprint.dataRefs.filterFunction), + ).toBe(mockFilter); + }); + + it('should allow overriding config and inputs', async () => { + const extension = EntityContentBlueprint.makeWithOverrides({ + name: 'test', + inputs: { + mock: createExtensionInput([coreExtensionData.reactElement]), + }, + config: { + schema: { + mock: z => z.string(), + }, + }, + factory(originalFactory, { inputs, config }) { + return originalFactory({ + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () => ( +
+ config: {config.mock} +
+ {inputs.mock.map((i, k) => ( +
{i.get(coreExtensionData.reactElement)}
+ ))} +
+
+ ), + }); + }, + }); + + const mockExtension = createExtension({ + attachTo: { id: 'entity-content:test', input: 'mock' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
im a mock
)]; + }, + }); + + createExtensionTester(extension, { config: { mock: 'mock test config' } }) + .add(mockExtension) + .render(); + + await waitFor(() => { + expect(screen.getByTestId('test')).toBeInTheDocument(); + expect(screen.getByTestId('test')).toHaveTextContent( + 'config: mock test config', + ); + expect(screen.getByTestId('contents')).toHaveTextContent('im a mock'); + }); + }); +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx new file mode 100644 index 0000000000..7c7a6c43b7 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { lazy } from 'react'; +import { + coreExtensionData, + createExtensionBlueprint, + ExtensionBoundary, + RouteRef, +} from '@backstage/frontend-plugin-api'; +import { catalogExtensionData } from '../extensions'; + +/** + * @alpha + * Creates an EntityContent extension. + */ +export const EntityContentBlueprint = createExtensionBlueprint({ + kind: 'entity-content', + attachTo: { id: 'page:catalog/entity', input: 'contents' }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + catalogExtensionData.entityContentTitle, + coreExtensionData.routeRef.optional(), + catalogExtensionData.entityFilterFunction.optional(), + catalogExtensionData.entityFilterExpression.optional(), + ], + dataRefs: { + title: catalogExtensionData.entityContentTitle, + filterFunction: catalogExtensionData.entityFilterFunction, + filterExpression: catalogExtensionData.entityFilterExpression, + }, + config: { + schema: { + path: z => z.string().optional(), + title: z => z.string().optional(), + filter: z => z.string().optional(), + }, + }, + *factory( + { + loader, + defaultPath, + defaultTitle, + filter, + routeRef, + }: { + loader: () => Promise; + defaultPath: string; + defaultTitle: string; + routeRef?: RouteRef; + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; + }, + { node, config }, + ) { + const path = config.path ?? defaultPath; + const title = config.title ?? defaultTitle; + + const ExtensionComponent = lazy(() => + loader().then(element => ({ default: () => element })), + ); + + yield coreExtensionData.reactElement( + + + , + ); + + yield coreExtensionData.routePath(path); + + yield catalogExtensionData.entityContentTitle(title); + + if (routeRef) { + yield coreExtensionData.routeRef(routeRef); + } + + if (config.filter) { + yield catalogExtensionData.entityFilterExpression(config.filter); + } else if (typeof filter === 'string') { + yield catalogExtensionData.entityFilterExpression(filter); + } else if (typeof filter === 'function') { + yield catalogExtensionData.entityFilterFunction(filter); + } + }, +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/index.ts b/plugins/catalog-react/src/alpha/blueprints/index.ts new file mode 100644 index 0000000000..dccaf28cd9 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { EntityCardBlueprint } from './EntityCardBlueprint'; +export { EntityContentBlueprint } from './EntityContentBlueprint'; diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha/extensions.tsx similarity index 93% rename from plugins/catalog-react/src/alpha.tsx rename to plugins/catalog-react/src/alpha/extensions.tsx index 42d69726b6..fa91494bd4 100644 --- a/plugins/catalog-react/src/alpha.tsx +++ b/plugins/catalog-react/src/alpha/extensions.tsx @@ -28,13 +28,16 @@ import { import React, { lazy } from 'react'; import { Entity } from '@backstage/catalog-model'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { Expand } from '../../../packages/frontend-plugin-api/src/types'; +import { Expand } from '../../../../packages/frontend-plugin-api/src/types'; -export { useEntityPermission } from './hooks/useEntityPermission'; -export { isOwnerOf } from './utils'; -export * from './translation'; +export { useEntityPermission } from '../hooks/useEntityPermission'; +export { isOwnerOf } from '../utils'; +export * from '../translation'; -/** @alpha */ +/** + * @alpha + * @deprecated use `dataRefs` on the blueprints instead + */ export const catalogExtensionData = { entityContentTitle: createExtensionDataRef().with({ id: 'catalog.entity-content-title', @@ -48,7 +51,10 @@ export const catalogExtensionData = { }; // TODO: Figure out how to merge with provided config schema -/** @alpha */ +/** + * @alpha + * @deprecated use {@link EntityCardBlueprint} instead + */ export function createEntityCardExtension< TConfig extends { filter?: string }, TInputs extends AnyExtensionInputMap, @@ -110,7 +116,10 @@ export function createEntityCardExtension< }); } -/** @alpha */ +/** + * @alpha + * @deprecated use {@link EntityContentBlueprint} instead + */ export function createEntityContentExtension< TInputs extends AnyExtensionInputMap, >(options: { diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts new file mode 100644 index 0000000000..132b288008 --- /dev/null +++ b/plugins/catalog-react/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './blueprints'; +export * from './extensions'; diff --git a/plugins/catalog-react/src/setupTests.ts b/plugins/catalog-react/src/setupTests.ts index 963c0f188b..dae4360fd3 100644 --- a/plugins/catalog-react/src/setupTests.ts +++ b/plugins/catalog-react/src/setupTests.ts @@ -15,3 +15,18 @@ */ import '@testing-library/jest-dom'; + +// eslint-disable-next-line no-console +const originalConsoleWarn = console.warn; +// eslint-disable-next-line no-console +console.warn = (...args: any[]) => { + const message = args[0]; + if ( + typeof message === 'string' && + (message.includes('CSSOM.parse is not a function') || + message.includes('[JSS]')) + ) { + return; + } + originalConsoleWarn(...args); +}; From 8f8519d94e227c7d0cab59c4cdddee5499cee283 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 14:27:47 +0200 Subject: [PATCH 239/393] feat: implement search Signed-off-by: blam --- .../catalog-react/src/alpha/extensions.tsx | 2 +- plugins/search-react/src/alpha.tsx | 51 +++----- .../SearchResultListItemBlueprint.test.tsx | 123 ++++++++++++++++++ .../SearchResultListItemBlueprint.tsx | 81 ++++++++++++ plugins/search-react/src/blueprints/index.ts | 25 ++++ plugins/search-react/src/blueprints/types.ts | 42 ++++++ plugins/search-react/src/setupTests.ts | 15 +++ yarn.lock | 2 + 8 files changed, 310 insertions(+), 31 deletions(-) create mode 100644 plugins/search-react/src/blueprints/SearchResultListItemBlueprint.test.tsx create mode 100644 plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx create mode 100644 plugins/search-react/src/blueprints/index.ts create mode 100644 plugins/search-react/src/blueprints/types.ts diff --git a/plugins/catalog-react/src/alpha/extensions.tsx b/plugins/catalog-react/src/alpha/extensions.tsx index fa91494bd4..e527edc355 100644 --- a/plugins/catalog-react/src/alpha/extensions.tsx +++ b/plugins/catalog-react/src/alpha/extensions.tsx @@ -36,7 +36,7 @@ export * from '../translation'; /** * @alpha - * @deprecated use `dataRefs` on the blueprints instead + * @deprecated use `dataRefs` property on either {@link EntityCardBlueprint} or {@link EntityContentBlueprint} instead */ export const catalogExtensionData = { entityContentTitle: createExtensionDataRef().with({ diff --git a/plugins/search-react/src/alpha.tsx b/plugins/search-react/src/alpha.tsx index a22eaf4244..53bf76f775 100644 --- a/plugins/search-react/src/alpha.tsx +++ b/plugins/search-react/src/alpha.tsx @@ -15,39 +15,25 @@ */ import React, { lazy } from 'react'; - -import { ListItemProps } from '@material-ui/core/ListItem'; - import { ExtensionBoundary, PortableSchema, createExtension, - createExtensionDataRef, createSchemaFromZod, } from '@backstage/frontend-plugin-api'; -import { SearchDocument, SearchResult } from '@backstage/plugin-search-common'; - import { SearchResultListItemExtension } from './extensions'; +import { + SearchResultItemExtensionComponent, + SearchResultItemExtensionPredicate, + searchResultListItemDataRef, +} from './blueprints/types'; -/** @alpha */ -export type BaseSearchResultListItemProps = T & { - rank?: number; - result?: SearchDocument; -} & Omit; +export * from './blueprints'; -/** @alpha */ -export type SearchResultItemExtensionComponent = < - P extends BaseSearchResultListItemProps, ->( - props: P, -) => JSX.Element | null; - -/** @alpha */ -export type SearchResultItemExtensionPredicate = ( - result: SearchResult, -) => boolean; - -/** @alpha */ +/** + * @alpha + * @deprecated Use {@link SearchResultListItemBlueprint} instead + */ export type SearchResultItemExtensionOptions< TConfig extends { noTrack?: boolean }, > = { @@ -80,7 +66,12 @@ export type SearchResultItemExtensionOptions< predicate?: SearchResultItemExtensionPredicate; }; -/** @alpha */ +/** + * Creates items for the search result list. + * + * @alpha + * @deprecated Use {@link SearchResultListItemBlueprint} instead + */ export function createSearchResultListItemExtension< TConfig extends { noTrack?: boolean }, >(options: SearchResultItemExtensionOptions) { @@ -132,10 +123,10 @@ export function createSearchResultListItemExtension< }); } -/** @alpha */ +/** + * @alpha + * @deprecated Use {@link SearchResultListItemBlueprint} instead + */ export namespace createSearchResultListItemExtension { - export const itemDataRef = createExtensionDataRef<{ - predicate?: SearchResultItemExtensionPredicate; - component: SearchResultItemExtensionComponent; - }>().with({ id: 'search.search-result-list-item.item' }); + export const itemDataRef = searchResultListItemDataRef; } diff --git a/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.test.tsx b/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.test.tsx new file mode 100644 index 0000000000..4f3b7501b4 --- /dev/null +++ b/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.test.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { SearchResultListItemBlueprint } from './SearchResultListItemBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + PageBlueprint, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { searchResultListItemDataRef } from './types'; +import _ from 'lodash'; + +describe('SearchResultListItemBlueprint', () => { + it('should return an extension with sane defaults', () => { + const extension = SearchResultListItemBlueprint.make({ + name: 'test', + params: { + component: async () => () =>
Hello
, + predicate: () => true, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "page:search", + "input": "items", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "noTrack": { + "default": false, + "type": "boolean", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "search-result-list-item", + "name": "test", + "namespace": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('defaults and passes on config properly', async () => { + const extension = SearchResultListItemBlueprint.make({ + name: 'test', + params: { + component: + async ({ config: { noTrack } }) => + () => +
noTrack: {String(noTrack)}
, + }, + }); + + const mockSearchPage = PageBlueprint.makeWithOverrides({ + namespace: 'search', + inputs: { + items: createExtensionInput([searchResultListItemDataRef]), + }, + factory(originalFactory, { inputs }) { + return originalFactory({ + defaultPath: '/', + loader: async () => { + const items = inputs.items.map(i => + i.get(searchResultListItemDataRef), + ); + return ( +
+ {items.map((item, i) => ( + + ))} +
+ ); + }, + }); + }, + }); + + await expect( + createExtensionTester(mockSearchPage) + .add(extension) + .render() + .findByText('noTrack: false'), + ).resolves.toBeInTheDocument(); + + await expect( + createExtensionTester(mockSearchPage) + .add(extension, { config: { noTrack: true } }) + .render() + .findByText('noTrack: true'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx b/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx new file mode 100644 index 0000000000..bea52b7f45 --- /dev/null +++ b/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { lazy } from 'react'; +import { + createExtensionBlueprint, + ExtensionBoundary, +} from '@backstage/frontend-plugin-api'; +import { + SearchResultItemExtensionComponent, + SearchResultItemExtensionPredicate, + searchResultListItemDataRef, +} from './types'; +import { + SearchResultListItemExtension, + SearchResultListItemExtensionProps, +} from '../extensions'; + +export interface SearchResultListItemBlueprintParams { + /** + * The extension component. + */ + component: (options: { + config: { noTrack?: boolean }; + }) => Promise; + /** + * When an extension defines a predicate, it returns true if the result should be rendered by that extension. + * Defaults to a predicate that returns true, which means it renders all sorts of results. + */ + predicate?: SearchResultItemExtensionPredicate; +} + +export const SearchResultListItemBlueprint = createExtensionBlueprint({ + kind: 'search-result-list-item', + attachTo: { + id: 'page:search', + input: 'items', + }, + config: { + schema: { + noTrack: z => z.boolean().default(false), + }, + }, + output: [searchResultListItemDataRef], + dataRefs: { + item: searchResultListItemDataRef, + }, + *factory(params: SearchResultListItemBlueprintParams, { config, node }) { + const ExtensionComponent = lazy(() => + params.component({ config }).then(component => ({ default: component })), + ); + + yield searchResultListItemDataRef({ + predicate: params.predicate, + component: (props: SearchResultListItemExtensionProps) => ( + + + + + + ), + }); + }, +}); diff --git a/plugins/search-react/src/blueprints/index.ts b/plugins/search-react/src/blueprints/index.ts new file mode 100644 index 0000000000..b9bbf1012e --- /dev/null +++ b/plugins/search-react/src/blueprints/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + SearchResultListItemBlueprint, + type SearchResultListItemBlueprintParams, +} from './SearchResultListItemBlueprint'; +export { + type BaseSearchResultListItemProps, + type SearchResultItemExtensionComponent, + type SearchResultItemExtensionPredicate, +} from './types'; diff --git a/plugins/search-react/src/blueprints/types.ts b/plugins/search-react/src/blueprints/types.ts new file mode 100644 index 0000000000..abfed4aba6 --- /dev/null +++ b/plugins/search-react/src/blueprints/types.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ListItemProps } from '@material-ui/core/ListItem'; +import { SearchDocument, SearchResult } from '@backstage/plugin-search-common'; +import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; + +/** @alpha */ +export type BaseSearchResultListItemProps = T & { + rank?: number; + result?: SearchDocument; +} & Omit; + +/** @alpha */ +export type SearchResultItemExtensionComponent = < + P extends BaseSearchResultListItemProps, +>( + props: P, +) => JSX.Element | null; + +/** @alpha */ +export type SearchResultItemExtensionPredicate = ( + result: SearchResult, +) => boolean; + +export const searchResultListItemDataRef = createExtensionDataRef<{ + predicate?: SearchResultItemExtensionPredicate; + component: SearchResultItemExtensionComponent; +}>().with({ id: 'search.search-result-list-item.item' }); diff --git a/plugins/search-react/src/setupTests.ts b/plugins/search-react/src/setupTests.ts index 992b60d3a4..c47fc7a427 100644 --- a/plugins/search-react/src/setupTests.ts +++ b/plugins/search-react/src/setupTests.ts @@ -15,3 +15,18 @@ */ import '@testing-library/jest-dom'; + +// eslint-disable-next-line no-console +const originalConsoleWarn = console.warn; +// eslint-disable-next-line no-console +console.warn = (...args: any[]) => { + const message = args[0]; + if ( + typeof message === 'string' && + (message.includes('CSSOM.parse is not a function') || + message.includes('[JSS]')) + ) { + return; + } + originalConsoleWarn(...args); +}; diff --git a/yarn.lock b/yarn.lock index 9c292f5903..dea942a3b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5921,6 +5921,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration-react": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" @@ -6001,6 +6002,7 @@ __metadata: "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration-react": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" From 4c39af89021284494227c9b03fd982241c54ea2c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 14:44:13 +0200 Subject: [PATCH 240/393] feat: small refactor for the search package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: blam --- plugins/catalog-react/api-report-alpha.md | 120 +++++- plugins/catalog/api-report-alpha.md | 365 +++++++++++++++++- plugins/search-react/api-report-alpha.md | 52 ++- plugins/search-react/package.json | 4 +- .../SearchResultListItemBlueprint.test.tsx | 0 .../SearchResultListItemBlueprint.tsx | 7 +- .../src/{ => alpha}/blueprints/index.ts | 0 .../src/{ => alpha}/blueprints/types.ts | 0 .../extensions.test.tsx} | 6 +- .../src/{alpha.tsx => alpha/extensions.tsx} | 11 +- plugins/search-react/src/alpha/index.ts | 17 + 11 files changed, 561 insertions(+), 21 deletions(-) rename plugins/search-react/src/{ => alpha}/blueprints/SearchResultListItemBlueprint.test.tsx (100%) rename plugins/search-react/src/{ => alpha}/blueprints/SearchResultListItemBlueprint.tsx (96%) rename plugins/search-react/src/{ => alpha}/blueprints/index.ts (100%) rename plugins/search-react/src/{ => alpha}/blueprints/types.ts (100%) rename plugins/search-react/src/{alpha.test.tsx => alpha/extensions.test.tsx} (97%) rename plugins/search-react/src/{alpha.tsx => alpha/extensions.tsx} (93%) create mode 100644 plugins/search-react/src/alpha/index.ts diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index edfa183903..f3d251dd0e 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -6,16 +6,19 @@ /// import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const catalogExtensionData: { entityContentTitle: ConfigurableExtensionDataRef< string, @@ -92,7 +95,7 @@ export const catalogReactTranslationRef: TranslationRef< } >; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export function createEntityCardExtension< TConfig extends { filter?: string; @@ -125,7 +128,7 @@ export function createEntityCardExtension< string | undefined >; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export function createEntityContentExtension< TInputs extends AnyExtensionInputMap, >(options: { @@ -164,6 +167,117 @@ export function createEntityContentExtension< string | undefined >; +// @alpha +export const EntityCardBlueprint: ExtensionBlueprint< + 'entity-card', + undefined, + undefined, + { + loader: () => Promise; + filter?: string | ((entity: Entity) => boolean) | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + { + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; + } +>; + +// @alpha +export const EntityContentBlueprint: ExtensionBlueprint< + 'entity-content', + undefined, + undefined, + { + loader: () => Promise; + defaultPath: string; + defaultTitle: string; + routeRef?: RouteRef | undefined; + filter?: string | ((entity: Entity) => boolean) | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + RouteRef & { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + { + title: ConfigurableExtensionDataRef< + string, + 'catalog.entity-content-title', + {} + >; + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; + } +>; + // @alpha export function isOwnerOf(owner: Entity, entity: Entity): boolean; diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 5019fc25ea..7c2f490939 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -6,13 +6,35 @@ /// import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; import { PortableSchema } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +// @alpha +export const CatalogFilterBlueprint: ExtensionBlueprint< + 'catalog-filter', + undefined, + undefined, + { + loader: () => Promise; + }, + ConfigurableExtensionDataRef, + {}, + {}, + {}, + never +>; + // @alpha (undocumented) export const catalogTranslationRef: TranslationRef< 'catalog', @@ -98,7 +120,9 @@ export const catalogTranslationRef: TranslationRef< } >; -// @alpha (undocumented) +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog" does not have an export "CatalogFilterExtensionBlueprint" +// +// @alpha @deprecated (undocumented) export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, TConfig, @@ -141,7 +165,344 @@ const _default: BackstagePlugin< }>; unregisterRedirect: ExternalRouteRef; }, - {} + { + 'entity-card:catalog/about': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'about' + >; + 'entity-card:catalog/links': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'links' + >; + 'entity-card:catalog/labels': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'labels' + >; + 'entity-card:catalog/depends-on-components': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'depends-on-components' + >; + 'entity-card:catalog/depends-on-resources': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'depends-on-resources' + >; + 'entity-card:catalog/has-components': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'has-components' + >; + 'entity-card:catalog/has-resources': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'has-resources' + >; + 'entity-card:catalog/has-subcomponents': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'has-subcomponents' + >; + 'entity-card:catalog/has-subdomains': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'has-subdomains' + >; + 'entity-card:catalog/has-systems': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'has-systems' + >; + 'entity-content:catalog/overview': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + [x: string]: any; + } & { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + RouteRef & { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + { + cards: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + ((entity: Entity) => boolean) & { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + string & { + optional: true; + } + >, + { + singleton: false; + optional: false; + } + >; + }, + 'entity-content', + undefined, + 'overview' + >; + } >; export default _default; diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index 4bcdbb3f78..0126fdc590 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -6,6 +6,7 @@ /// import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ListItemProps } from '@material-ui/core/ListItem'; import { PortableSchema } from '@backstage/frontend-plugin-api'; @@ -18,7 +19,7 @@ export type BaseSearchResultListItemProps = T & { result?: SearchDocument; } & Omit; -// @alpha (undocumented) +// @alpha @deprecated export function createSearchResultListItemExtension< TConfig extends { noTrack?: boolean; @@ -35,9 +36,9 @@ export function createSearchResultListItemExtension< string | undefined >; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export namespace createSearchResultListItemExtension { - const // (undocumented) + const // @deprecated (undocumented) itemDataRef: ConfigurableExtensionDataRef< { predicate?: SearchResultItemExtensionPredicate | undefined; @@ -55,7 +56,7 @@ export type SearchResultItemExtensionComponent = < props: P, ) => JSX.Element | null; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export type SearchResultItemExtensionOptions< TConfig extends { noTrack?: boolean; @@ -79,5 +80,48 @@ export type SearchResultItemExtensionPredicate = ( result: SearchResult, ) => boolean; +// @alpha +export const SearchResultListItemBlueprint: ExtensionBlueprint< + 'search-result-list-item', + undefined, + undefined, + SearchResultListItemBlueprintParams, + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + {}, + { + noTrack: boolean; + }, + { + noTrack?: boolean | undefined; + }, + { + item: ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >; + } +>; + +// @alpha (undocumented) +export interface SearchResultListItemBlueprintParams { + component: (options: { + config: { + noTrack?: boolean; + }; + }) => Promise; + predicate?: SearchResultItemExtensionPredicate; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index e16430c55b..887b41cb83 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -28,7 +28,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.tsx", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -36,7 +36,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.tsx" + "src/alpha/index.ts" ], "package.json": [ "package.json" diff --git a/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx similarity index 100% rename from plugins/search-react/src/blueprints/SearchResultListItemBlueprint.test.tsx rename to plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx diff --git a/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx similarity index 96% rename from plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx rename to plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx index bea52b7f45..b9a7b71719 100644 --- a/plugins/search-react/src/blueprints/SearchResultListItemBlueprint.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx @@ -27,8 +27,9 @@ import { import { SearchResultListItemExtension, SearchResultListItemExtensionProps, -} from '../extensions'; +} from '../../extensions'; +/** @alpha */ export interface SearchResultListItemBlueprintParams { /** * The extension component. @@ -43,6 +44,10 @@ export interface SearchResultListItemBlueprintParams { predicate?: SearchResultItemExtensionPredicate; } +/** + * @alpha + * Creates SearchResultListItem extensions + */ export const SearchResultListItemBlueprint = createExtensionBlueprint({ kind: 'search-result-list-item', attachTo: { diff --git a/plugins/search-react/src/blueprints/index.ts b/plugins/search-react/src/alpha/blueprints/index.ts similarity index 100% rename from plugins/search-react/src/blueprints/index.ts rename to plugins/search-react/src/alpha/blueprints/index.ts diff --git a/plugins/search-react/src/blueprints/types.ts b/plugins/search-react/src/alpha/blueprints/types.ts similarity index 100% rename from plugins/search-react/src/blueprints/types.ts rename to plugins/search-react/src/alpha/blueprints/types.ts diff --git a/plugins/search-react/src/alpha.test.tsx b/plugins/search-react/src/alpha/extensions.test.tsx similarity index 97% rename from plugins/search-react/src/alpha.test.tsx rename to plugins/search-react/src/alpha/extensions.test.tsx index 6d77b5af97..5194fadd1c 100644 --- a/plugins/search-react/src/alpha.test.tsx +++ b/plugins/search-react/src/alpha/extensions.test.tsx @@ -23,10 +23,8 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { SearchResult } from '@backstage/plugin-search-common'; import { screen } from '@testing-library/react'; import React from 'react'; -import { - BaseSearchResultListItemProps, - createSearchResultListItemExtension, -} from './alpha'; +import { createSearchResultListItemExtension } from './extensions'; +import { BaseSearchResultListItemProps } from './blueprints'; describe('createSearchResultListItemExtension', () => { it('Should use the correct result component', async () => { diff --git a/plugins/search-react/src/alpha.tsx b/plugins/search-react/src/alpha/extensions.tsx similarity index 93% rename from plugins/search-react/src/alpha.tsx rename to plugins/search-react/src/alpha/extensions.tsx index 53bf76f775..db09510a1e 100644 --- a/plugins/search-react/src/alpha.tsx +++ b/plugins/search-react/src/alpha/extensions.tsx @@ -21,14 +21,12 @@ import { createExtension, createSchemaFromZod, } from '@backstage/frontend-plugin-api'; -import { SearchResultListItemExtension } from './extensions'; import { SearchResultItemExtensionComponent, SearchResultItemExtensionPredicate, - searchResultListItemDataRef, -} from './blueprints/types'; - -export * from './blueprints'; +} from './blueprints'; +import { SearchResultListItemExtension } from '../extensions'; +import { searchResultListItemDataRef } from './blueprints/types'; /** * @alpha @@ -128,5 +126,8 @@ export function createSearchResultListItemExtension< * @deprecated Use {@link SearchResultListItemBlueprint} instead */ export namespace createSearchResultListItemExtension { + /** + * @deprecated Use {@link SearchResultListItemBlueprint#dataRefs.item} instead + */ export const itemDataRef = searchResultListItemDataRef; } diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts new file mode 100644 index 0000000000..04026d629d --- /dev/null +++ b/plugins/search-react/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './extensions'; +export * from './blueprints'; From 7bd27e1743039fcb0405330f355bc669f4675979 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 14:45:34 +0200 Subject: [PATCH 241/393] chore: changeset Signed-off-by: blam --- .changeset/curly-cars-relax.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/curly-cars-relax.md diff --git a/.changeset/curly-cars-relax.md b/.changeset/curly-cars-relax.md new file mode 100644 index 0000000000..de1b4c3d89 --- /dev/null +++ b/.changeset/curly-cars-relax.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-catalog': patch +--- + +Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. From 896a84bce572dbf0789d72581d3ca109f9b178c1 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 14:49:51 +0200 Subject: [PATCH 242/393] chore: small fix Signed-off-by: blam --- plugins/catalog/api-report-alpha.md | 2 -- plugins/catalog/src/alpha/createCatalogFilterExtension.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 7c2f490939..57560ad6a0 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -120,8 +120,6 @@ export const catalogTranslationRef: TranslationRef< } >; -// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog" does not have an export "CatalogFilterExtensionBlueprint" -// // @alpha @deprecated (undocumented) export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx index eb6d58f424..85d911ef3b 100644 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx @@ -25,7 +25,7 @@ import { /** * @alpha - * @deprecated Use {@link CatalogFilterExtensionBlueprint} instead + * @deprecated Use {@link CatalogFilterBlueprint} instead */ export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, From 45a469fc3bd4ebe703cf8af5a9ebc9ec6aa402f9 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 16:25:56 +0200 Subject: [PATCH 243/393] chore: update apireports Signed-off-by: blam --- plugins/catalog-react/api-report-alpha.md | 10 ++--- plugins/catalog/api-report-alpha.md | 50 +++++++++++------------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index f3d251dd0e..c5dc632946 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -180,14 +180,14 @@ export const EntityCardBlueprint: ExtensionBlueprint< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -229,7 +229,7 @@ export const EntityContentBlueprint: ExtensionBlueprint< | ConfigurableExtensionDataRef< RouteRef, 'core.routing.ref', - RouteRef & { + { optional: true; } > @@ -237,14 +237,14 @@ export const EntityContentBlueprint: ExtensionBlueprint< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 57560ad6a0..399580d6a2 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -175,14 +175,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -202,14 +202,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -229,14 +229,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -256,14 +256,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -283,14 +283,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -310,14 +310,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -337,14 +337,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -364,14 +364,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -391,14 +391,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -418,14 +418,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -454,7 +454,7 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< RouteRef, 'core.routing.ref', - RouteRef & { + { optional: true; } > @@ -462,14 +462,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, @@ -479,14 +479,14 @@ const _default: BackstagePlugin< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', - ((entity: Entity) => boolean) & { + { optional: true; } > | ConfigurableExtensionDataRef< string, 'catalog.entity-filter-expression', - string & { + { optional: true; } >, From 954a5939db56fc12d3cdc4f3ab6c03e1e71f945f Mon Sep 17 00:00:00 2001 From: Deepankumar Date: Thu, 15 Aug 2024 10:10:48 +0200 Subject: [PATCH 244/393] Liveness probe added in ContainerCard component (#25958) * Liveness probe added in ContainerCard component Signed-off-by: Deepankumar Loganathan * liveness probe shows only if available Signed-off-by: Deepankumar Loganathan * changed return object from variable Signed-off-by: Deepankumar Loganathan --------- Signed-off-by: Deepankumar Loganathan --- .changeset/eight-moose-pull.md | 5 ++++ .../Pods/PodDrawer/ContainerCard.test.tsx | 1 + .../Pods/PodDrawer/ContainerCard.tsx | 27 +++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 .changeset/eight-moose-pull.md diff --git a/.changeset/eight-moose-pull.md b/.changeset/eight-moose-pull.md new file mode 100644 index 0000000000..c5bc5ee66e --- /dev/null +++ b/.changeset/eight-moose-pull.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +`Liveness Probe` added in ContainerCard Component of PodDrawer diff --git a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx index 6d64b8ad5b..3b131a617c 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx @@ -40,6 +40,7 @@ describe('ContainerCard', () => { }, containerSpec: { readinessProbe: {}, + livenessProbe: {}, }, containerStatus: { name: 'some-name', diff --git a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx index cabda69b2c..1919bc112d 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx @@ -35,20 +35,25 @@ const getContainerHealthChecks = ( containerSpec: IContainer, containerStatus: IContainerStatus, ): { [key: string]: boolean } => { - if (containerStatus.state?.terminated?.reason === 'Completed') { - return { - 'not waiting to start': containerStatus.state?.waiting === undefined, - 'no restarts': containerStatus.restartCount === 0, - }; - } - return { + const healthCheck = { 'not waiting to start': containerStatus.state?.waiting === undefined, - started: !!containerStatus.started, - ready: containerStatus.ready, 'no restarts': containerStatus.restartCount === 0, - 'readiness probe set': - containerSpec && containerSpec?.readinessProbe !== undefined, }; + if (containerStatus.state?.terminated?.reason === 'Completed') { + return healthCheck; + } + Object.assign( + healthCheck, + { started: !!containerStatus.started }, + { ready: containerStatus.ready }, + { 'readiness probe set': containerSpec?.readinessProbe !== undefined }, + ); + if (containerSpec && containerSpec?.livenessProbe !== undefined) { + Object.assign(healthCheck, { + 'liveness probe set': containerSpec.livenessProbe, + }); + } + return healthCheck; }; const getCurrentState = (containerStatus: IContainerStatus): string => { From 232eea92e6c1160664278bd099889139835ea39b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 10:18:22 +0200 Subject: [PATCH 245/393] docs/frontend-system: migration docs for extension creators Signed-off-by: Patrik Oldsberg --- docs/frontend-system/architecture/20-extensions.md | 4 ---- docs/frontend-system/architecture/60-migrations.md | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index d1024d5e17..b43be74dbc 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -338,7 +338,3 @@ export function createSomeExtension< }); } ``` - -### Extension Creators (Deprecated) - -Previous iterations of the frontend system used an "extension creator" pattern, where `createExtension` was wrapped up in a function for creating specific extension kinds. This pattern was similar to [blueprints](./23-extension-blueprints.md), but much harder to implement and maintain. It has been deprecated in favor of the new blueprints API. For example, `createPageExtension` was the extension creator equivalent of `PageBlueprint`. diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md index 6ba3c73f0e..1c1e61e4d7 100644 --- a/docs/frontend-system/architecture/60-migrations.md +++ b/docs/frontend-system/architecture/60-migrations.md @@ -72,3 +72,11 @@ createExtension({ Note the changes to the `inputs` and `output` declarations, as well as how these are used in the factory. Both the `inputs` and `output` now declare their expected data using an array, without names tied to each piece of data. The `get` method on the input object is used to access the input data, and accept a data reference matching the data that was declared for that input. The biggest change in practice is how the extension factories output their data. Instead of returning an object with the data values, you instead use each extension data reference to encapsulate a value and return a collection of these encapsulated values from the factory. + +### Blueprints instead of extension creators + +The "extension creator" pattern where `createExtension` was wrapped up in a function for creating specific kind of extensions has been replaced by [extension blueprints](./23-extension-blueprints.md). Extension creators were hard to implement and to maintain, with blueprints providing a much more consistent and powerful API surface for both plugin builders and integrators. For example, `createPageExtension` was the extension creator equivalent of `PageBlueprint`. + +Replace all existing usages of extension creators in your plugin or app with the corresponding blueprint. For a given extension creator `createExtension`, the blueprint equivalent is `Blueprint`. There might be slight differences in the API between the two, but most often you just need to move the kind-specific options of the extension creator to be passed as parameters to the blueprint. Note that if your extension declared any additional inputs or config you'll need to use the [`.makeWithOverrides`](./23-extension-blueprints.md#creating-an-extension-from-a-blueprint-with-overrides) method of the blueprint. + +If your plugin exports and extension creators, these should be migrated to blueprints instead. From d4076c7d08365037b235fa491f6ff9af0f9f8441 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 10:24:19 +0200 Subject: [PATCH 246/393] docs/frontend-system: migration docs for extension tester Signed-off-by: Patrik Oldsberg --- docs/frontend-system/architecture/60-migrations.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md index 1c1e61e4d7..0d6ef39209 100644 --- a/docs/frontend-system/architecture/60-migrations.md +++ b/docs/frontend-system/architecture/60-migrations.md @@ -80,3 +80,9 @@ The "extension creator" pattern where `createExtension` was wrapped up in a func Replace all existing usages of extension creators in your plugin or app with the corresponding blueprint. For a given extension creator `createExtension`, the blueprint equivalent is `Blueprint`. There might be slight differences in the API between the two, but most often you just need to move the kind-specific options of the extension creator to be passed as parameters to the blueprint. Note that if your extension declared any additional inputs or config you'll need to use the [`.makeWithOverrides`](./23-extension-blueprints.md#creating-an-extension-from-a-blueprint-with-overrides) method of the blueprint. If your plugin exports and extension creators, these should be migrated to blueprints instead. + +### Extension tester rework + +The `createExtensionTester` from the `@backstage/frontend-test-utils` package has been reworked to better support testing of extensions. The new API allows access to extension output directly using the new `.get(ref)` method, and also provides access to all tested extensions through the new `.query(id/extension)` method. + +The `.render()` method that used to render the test subject in a test app has been deprecated. The extension tester no longer constructs a full app tree, but instead only instantiates the tree for the extensions under test. If you want to test the rendering of an extension that outputs in an app, you can instead use the `renderInTestApp` utility in combination with the new `.reactElement()` method of the extension tester: `renderInTestApp(tester.reactElement())`. From 160c55181a62f76c8722d5886a5bc3ed8c89e45a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 10:27:11 +0200 Subject: [PATCH 247/393] docs/frontend-system: migration docs for extension data refs Signed-off-by: Patrik Oldsberg --- .../architecture/60-migrations.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md index 0d6ef39209..a1ab0197f7 100644 --- a/docs/frontend-system/architecture/60-migrations.md +++ b/docs/frontend-system/architecture/60-migrations.md @@ -86,3 +86,19 @@ If your plugin exports and extension creators, these should be migrated to bluep The `createExtensionTester` from the `@backstage/frontend-test-utils` package has been reworked to better support testing of extensions. The new API allows access to extension output directly using the new `.get(ref)` method, and also provides access to all tested extensions through the new `.query(id/extension)` method. The `.render()` method that used to render the test subject in a test app has been deprecated. The extension tester no longer constructs a full app tree, but instead only instantiates the tree for the extensions under test. If you want to test the rendering of an extension that outputs in an app, you can instead use the `renderInTestApp` utility in combination with the new `.reactElement()` method of the extension tester: `renderInTestApp(tester.reactElement())`. + +### Extension data references update + +The way that extension data references are declared has been changed to allow for type inference of the ID. This requires the declaration to be split into two separate function calls, one to supply the type and the other to infer any options. For example, a reference that was previously declared like this: + +```ts +export const myExtension = createExtensionDataRef('my-plugin.my-data'); +``` + +Should be updated to the following: + +```ts +export const myExtension = createExtensionDataRef().with({ + id: 'my-plugin.my-data', +}); +``` From 4109dfb00afcf96e51847fe86d3524a7bc6c0d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 15 Aug 2024 10:53:56 +0200 Subject: [PATCH 248/393] bump the elliptic dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4c47bc5771..18e6b613a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25756,8 +25756,8 @@ __metadata: linkType: hard "elliptic@npm:^6.0.0, elliptic@npm:^6.5.4": - version: 6.5.4 - resolution: "elliptic@npm:6.5.4" + version: 6.5.7 + resolution: "elliptic@npm:6.5.7" dependencies: bn.js: ^4.11.9 brorand: ^1.1.0 @@ -25766,7 +25766,7 @@ __metadata: inherits: ^2.0.4 minimalistic-assert: ^1.0.1 minimalistic-crypto-utils: ^1.0.1 - checksum: d56d21fd04e97869f7ffcc92e18903b9f67f2d4637a23c860492fbbff5a3155fd9ca0184ce0c865dd6eb2487d234ce9551335c021c376cd2d3b7cb749c7d10f4 + checksum: af0ffddffdbc2fea4eeec74388cd73e62ed5a0eac6711568fb28071566319785df529c968b0bf1250ba4bc628e074b2d64c54a633e034aa6f0c6b152ceb49ab8 languageName: node linkType: hard From 5590f4ec7b2067876652472996af28738327c949 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 12:44:35 +0200 Subject: [PATCH 249/393] docs/frontend-system: update extension docs Signed-off-by: Patrik Oldsberg --- .../architecture/20-extensions.md | 227 +++++++++--------- .../architecture/50-naming-patterns.md | 2 +- 2 files changed, 118 insertions(+), 111 deletions(-) diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index b43be74dbc..ac55a42827 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -18,12 +18,12 @@ Each extensions has a number of different properties that define how it behaves ### ID - - The ID of an extension is used to uniquely identity it, and it should ideally be unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics. +When creating an extension do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extensions) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. + +The extension ID will be constructed using the pattern `[:][][/][]`, where the separating `/` is only present if both a namespace and name are defined. + ### Output The output of an extension is the data that it provides to its parent extension, and ultimately its contribution to the app. The output itself comes in the form of a collection of arbitrary values, anything that can be represented as a TypeScript type. However, each individual output value must be associated with a shared reference known as an extension data reference. You must also use these same references to be able to access individual output values of an extension. @@ -32,7 +32,7 @@ The output of an extension is the data that it provides to its parent extension, The inputs of an extension define the data that it received from its children. Each extension can have multiple different inputs identified by an input name. These inputs each have their own set of data that they expect, which is defined as a collection of extension data references. An extension will only have access to the data that it has explicitly requested from each input. -### Attachment Point +### Attachment point The attachment point of an extension decides where in the app extension tree it will be located. It is defined by the ID of the parent extension, as well as the name of the input to attach to. Through the attachment point the extension will share its own output as inputs to the parent extension. An extension can only be attached to an input that matches its own output, it is an error to try to attach an extension to an input the requires data that the extension does not provide in its output. @@ -44,7 +44,7 @@ Each extension in the app can be disabled, meaning it will not be instantiated a The ordering of extensions is sometimes very important, as it may for example affect in which order they show up in the UI. When an extension is toggled from disabled to enabled through configuration it resets the ordering of the extension, pushing it to the end of the list. It is generally recommended to leave extensions as disabled by default if their order is important, allowing for the order in which their are enabled in the configuration to determine their order in the app. -### Configuration & Configuration Schema +### Configuration & configuration schema Each extension can define a configuration schema that describes the configuration that it accepts. This schema is used to validate the configuration provided by integrators, but also to fill in default configuration values. The configuration itself is provided by integrators in order to customize the extension. It is not possible to provide a default configuration of an extension, this must instead be done through defaults in the configuration schema. This allows for a simpler configuration logic where multiple configurations of the same extension completely replace each other rather than being merged. @@ -54,7 +54,7 @@ The extension factory is the implementation of the extension itself. It is a fun Extension factories should be lean and not do any heavy lifting or async work, as they are called during the initialization of the app. For example, if you need to do an expensive computation to generate your output, then prefer outputting a callback that does the computation instead. This allows the parent extension to defer the computation for later so that you avoid blocking the app startup. -## Creating an Extensions +## Creating an extension Extensions are created using the `createExtension` function from `@backstage/frontend-plugin-api`. At minimum you need to provide an ID, attachment point, output definition, and a factory function. The following example shows the creation of a minimal extension: @@ -64,28 +64,22 @@ const extension = createExtension({ // This is the attachment point, `id` is the ID of the parent extension, // while `input` is the name of the input to attach to. attachTo: { id: 'my-parent', input: 'content' }, - // The output map defines the outputs of the extension. The object keys - // are only used internally to map the outputs of the factory and do - // not need to match the keys of the input. - output: { - element: coreExtensionData.reactElement, - }, + // The output option defines the allowed and required outputs of the extension factory. + output: [coreExtensionData.reactElement], // This factory is called to instantiate the extensions and produce its output. factory() { - return { - element:
Hello World
, - }; + return [coreExtensionData.reactElement(
Hello World
)]; }, }); ``` -Note that while the `createExtension` is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many extension creator functions exported by both the core APIs and plugins that make it easier to create extensions for more specific usages. +Note that while the `createExtension` is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many [extension blueprints](./23-extension-blueprints.md) exported by both the core APIs and plugins that make it easier to create extensions for more specific usages. -## Extension Data +## Extension data Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extensions. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions. -### Extension Data References +### Extension data references To create a new extension data reference to represent a type of shared extension data you use the `createExtensionDataRef` function. When defining a new reference you need to provide an ID and a TypeScript type, for example: @@ -101,59 +95,55 @@ The `ExtensionDataRef` can then be used to describe an output property of the ex ```tsx const extension = createExtension({ // ... - output: { - element: reactElementExtensionDataRef, - }, + output: [reactElementExtensionDataRef], factory() { - return { - element:
Hello World
, - }; + return [reactElementExtensionDataRef(
Hello World
)]; }, }); ``` -### Extension Data Uniqueness +### Extension data uniqueness -Note that the key used in the output map, in this case `element`, is only used internally within the definition of the extension itself. That actual identifier for the data when consumed by other extensions is the ID of the reference, in this case [`core.reactElement`](https://github.com/backstage/backstage/blob/916da47e8abdb880877daa18881eb8fdbb33e70a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts#L23). This means that you can not output multiple different values for the same extension data reference, as they would conflict with each other. That in turn makes overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. +Note that you are **not** allowed to repeat the same data reference in the outputs, or return multiple values for the same reference. Multiple outputs for the same reference will conflict with each other and cause an error. If you want to output multiple values of the same TypeScript type you should create separate references for each value. That in turn means that overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. ```tsx const extension = createExtension({ // ... - output: { - // ❌ Bad example - outputting values of same type - element1: reactElementExtensionDataRef, - element2: reactElementExtensionDataRef, - }, + output: [ + // ❌ Bad example - duplicate output declaration + reactElementExtensionDataRef, + reactElementExtensionDataRef, + ], factory() { - return { - element1:
Hello World
, - element2:
Hello World
, - }; + return [ + // ❌ Bad example - duplicate output values + reactElementExtensionDataRef(
Hello
), + reactElementExtensionDataRef(
World
), + ]; }, }); ``` -### Core Extension Data +### Core extension data We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. For a full list and explanations of all types of core extension data, see the [core extension data reference](../building-plugins/04-built-in-data-refs.md). -### Optional Extension Data +### Optional extension data By default all extension data is required, meaning that the extension factory must provide a value for each output. However, it is possible to make extension data optional by calling the `.optional()` method. This makes it optional for the factory function to return a value as part of its output. When calling the `.optional()` method you create a new copy of the extension data reference, it does not mutate the existing reference. ```tsx const extension = createExtension({ // ... - output: { - element: coreExtensionData.reactElement.optional(), - }, + output: [coreExtensionData.reactElement.optional()], factory() { - return { - element: + return [ + coreExtensionData.reactElement.optional()( Math.random() < 0.5 ? : undefined, - }; + ), + ]; }, }); ``` @@ -167,17 +157,19 @@ const navigationExtension = createExtension({ // ... inputs: { // [1]: Input - logo: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true, optional: true }, - ), + logo: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), }, factory({ inputs }) { return { element: ( - + ), }; }, @@ -193,10 +185,9 @@ So how can we now attach the output to the parent extension's input? If we think const navigationItemExtension = createExtension({ // ... attachTo: { id: 'app/nav', input: 'items' }, + output: [coreExtensionData.reactElement], factory() { - return { - element: Home, - }; + return [coreExtensionData.reactElement(Home)]; }, }); @@ -205,26 +196,22 @@ const navigationExtension = createExtension({ // [2]: Extension `id` will be `app/nav` following the extension naming pattern namespace: 'app', name: 'nav', - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], inputs: { - items: createExtensionInput({ - element: coreExtensionData.reactElement, - }), + items: createExtensionInput([coreExtensionData.reactElement]), }, factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, // ... }); @@ -232,27 +219,27 @@ const navigationExtension = createExtension({ In this case the extension input `items` is an array, where each individual item is an extension that attached itself to the extension inputs of this `id`. -With the `inputs` not only the `output` of an extensions item is passed to the extension, but also the `node`. However, it is discouraged to consume the `node` here unless needed. If we are looking at the `factory` function from the example above we could access the `node` like the following: +In addition to being able to access data passed through the input, you also have access to the underlying app `node`. This can be useful if you for example want to get the ID of the attached extension. However, avoid using the `node` unless needed, it is generally better to stick to only consuming the provided data. If we are looking at the `factory` function from the example above we could access the `node` like the following: ```tsx // ... factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( ), - }; + ]; }, ``` -## Extension Configuration +## Extension configuration With the `app-config.yaml` there is already the option to pass configuration to plugins or the app to e.g. define the `baseURL` of your app. For extensions this concept would be limiting as an extension can be independent of the plugin & initiated several times. Therefore we created a possibility to configure each extension individually through config. The extension config schema is created using the [`zod`](https://zod.dev/) library, which in addition to TypeScript type checking also provides runtime validation and coercion. If we continue with the example of the `navigationExtension` and now want it to contain a configurable title, we could make it available like the following: @@ -262,20 +249,20 @@ const navigationExtension = createExtension({ namespace: 'app', name: 'nav', // [3]: Extension `id` will be `app/nav` following the extension naming pattern - configSchema: createSchemaFromZod(z => - z.object({ - title: z.string().default('Sidebar Title'), - }), - ), + config: { + schema: { + title: z => z.string().default('Sidebar Title'), + }, + }, factory({ config }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, // ... }); @@ -293,15 +280,40 @@ app: title: 'Backstage' ``` -## Extension Boundary +## Extension factory as a generator function + +In all examples so far we have defined the extension factory as a regular function that returns its output in an array. However, the only requirement is that the factory function returns an iterable of extension data value. This means that you can also define the factory function as a generator function, which allows you to yield values one by one. This is particularly useful if you want to conditionally output values. + +For example, this is how we could define an extension where its output depends on the configuration: + +```tsx +const exampleExtension = createExtension({ + // ... + config: { + schema: { + disableIcon: z.boolean().default(false), + }, + }, + output: [coreExtensionData.reactElement, iconDataRef.optional()], + *factory({ config }) { + yield coreExtensionData.reactElement(
Hello World
); + + if (!config.disableIcon) { + yield iconDataRef(); + } + }, +}); +``` + +## Extension boundary The `ExtensionBoundary` wraps extensions with several React contexts for different purposes ### Suspense -All React elements rendered by extension creators should be wrapped in the extension boundary. With `Suspense` the extension can than load resources asynchronously with having a loading fallback. It also allows to lazy load the whole extension similar to how plugins are currently lazy loaded in Backstage. +Most React elements rendered by extensions should be wrapped in the extension boundary. With `Suspense` the extension can than load resources asynchronously with having a loading fallback. It also allows to lazy load the whole extension similar to how plugins are currently lazy loaded in Backstage. -### Error Boundary +### Error boundary Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallback component in case there is an uncaught error inside of the component. With this the error can be isolated & it would prevent the rest of the plugin to crash. @@ -309,32 +321,27 @@ Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallbac Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered `mountPoint`. Whether an extension is routable is inferred from its outputs, but you can also explicitly control this behavior by passing the `routable` prop to `ExtensionBoundary`. -The `ExtensionBoundary` can be used like the following in an extension creator: +The `ExtensionBoundary` can be used like the following in an extension: ```tsx -export function createSomeExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options): ExtensionDefinition { - return createExtension({ - // ... - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); +const routableExtension = createExtension({ + // ... + factory({ config, inputs, node }) { + const ExtensionComponent = lazy(() => + options + .loader({ config, inputs }) + .then(element => ({ default: () => element })), + ); - return { - path: config.path, - routeRef: options.routeRef, - element: ( - - - - ), - }; - }, - }); -} + return [ + coreExtensionData.path(config.path), + coreExtensionData.routeRef(options.routeRef), + coreExtensionData.reactElement( + + + , + ), + ]; + }, +}); ``` diff --git a/docs/frontend-system/architecture/50-naming-patterns.md b/docs/frontend-system/architecture/50-naming-patterns.md index b21a01c0d2..11e0b8e733 100644 --- a/docs/frontend-system/architecture/50-naming-patterns.md +++ b/docs/frontend-system/architecture/50-naming-patterns.md @@ -42,7 +42,7 @@ Note that while we use this naming pattern for the plugin instance this is only | ID | `[:][/]` | `'core.nav'`, `'page:user-settings'`, `'entity-card:catalog/about'` | | Symbol | `[][]` | `coreNav`, `userSettingsPage`, `catalogAboutEntityCard` | -When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the blueprint creator, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. +When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the extension blueprint, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. Example: From 4bc4cd969c953185d90d1a82ec4fb3ca9b361f51 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Aug 2024 14:49:56 +0200 Subject: [PATCH 250/393] chore: wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Co-authored-by: Johan Haals Signed-off-by: blam --- .../blueprints/SignInPageBlueprint.test.tsx | 23 ++-- .../src/app/createExtensionTester.tsx | 71 +++-------- .../src/app/renderInTestApp.tsx | 113 ++++++++++++++++-- 3 files changed, 128 insertions(+), 79 deletions(-) diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx index f97798a64c..61b23e56f9 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx @@ -16,9 +16,11 @@ import React from 'react'; import { SignInPageBlueprint } from './SignInPageBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { screen, waitFor } from '@testing-library/react'; -import { coreExtensionData, createExtension } from '../wiring'; describe('SignInPageBlueprint', () => { it('should create an extension with sensible defaults', () => { @@ -60,20 +62,11 @@ describe('SignInPageBlueprint', () => { const tester = createExtensionTester(extension); - expect(tester.data(SignInPageBlueprint.dataRefs.component)).toBeDefined(); + const Element = tester.data(SignInPageBlueprint.dataRefs.component)!; - createExtensionTester( - createExtension({ - name: 'dummy', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element:
}), - }), - ) - .add(extension) - .render(); + expect(Element).toBeDefined(); + + renderInTestApp( {}} />); await waitFor(() => { expect(screen.getByTestId('mock-sign-in')).toBeInTheDocument(); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 4bfc8392cb..2d842062fc 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -49,57 +49,6 @@ import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/insta // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; -const NavItem = (props: { - routeRef: RouteRef; - title: string; - icon: IconComponent; -}) => { - const { routeRef, title, icon: Icon } = props; - const link = useRouteRef(routeRef); - if (!link) { - return null; - } - return ( -
  • - - {title} - -
  • - ); -}; - -const TestAppNavExtension = createExtension({ - namespace: 'app', - name: 'nav', - attachTo: { id: 'app/layout', input: 'nav' }, - inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - ), - }; - }, -}); - /** @public */ export class ExtensionQuery { #node: AppNode; @@ -245,6 +194,25 @@ export class ExtensionTester { return new ExtensionQuery(node); } + element(): JSX.Element { + const tree = this.#resolveTree(); + + const element = new ExtensionQuery(tree.root).data( + coreExtensionData.reactElement, + ); + + if (!element) { + throw new Error( + 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(myComponentDataRef)` method to get the component', + ); + } + + return element; + } + + /** + * @deprecated Switch to using `renderInTestApp` directly and using `.element()` or `.get(myComponentDataRef)` to get the component you would like to wrap up + */ render(options?: { config?: JsonObject }): RenderResult { const { config = {} } = options ?? {}; @@ -260,7 +228,6 @@ export class ExtensionTester { createExtensionOverrides({ extensions: [ ...this.#extensions.map(extension => extension.definition), - TestAppNavExtension, createRouterExtension({ namespace: 'test', Component: ({ children }) => ( diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index e0483f68d8..d828c89f12 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -16,11 +16,24 @@ import React from 'react'; import { + ExtensionDefinition, + IconComponent, RouteRef, coreExtensionData, createExtension, + createExtensionInput, + createExtensionOverrides, + createNavItemExtension, + createRouterExtension, + useRouteRef, } from '@backstage/frontend-plugin-api'; -import { createExtensionTester } from './createExtensionTester'; +import { Link, MemoryRouter } from 'react-router-dom'; +import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { render } from '@testing-library/react'; +import { resolveExtensionDefinition } from '@backstage/frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +import { resolve } from 'path'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; /** * Options to customize the behavior of the test app. @@ -44,8 +57,64 @@ export type TestAppOptions = { * ``` */ mountedRoutes?: { [path: string]: RouteRef }; + + /** + * Additional configuration passed to the app when rendering elements inside it. + */ + config?: JsonObject; }; +const NavItem = (props: { + routeRef: RouteRef; + title: string; + icon: IconComponent; +}) => { + const { routeRef, title, icon: Icon } = props; + const link = useRouteRef(routeRef); + if (!link) { + return null; + } + return ( +
  • + + {title} + +
  • + ); +}; + +const TestAppNavExtension = createExtension({ + namespace: 'app', + name: 'nav', + attachTo: { id: 'app/layout', input: 'nav' }, + inputs: { + items: createExtensionInput({ + target: createNavItemExtension.targetDataRef, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( + + ), + }; + }, +}); + /** * @public * Renders the given element in a test app, for use in unit tests. @@ -54,20 +123,28 @@ export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, ) { - const extension = createExtension({ - namespace: 'test', - attachTo: { id: 'app', input: 'root' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element }), - }); - const tester = createExtensionTester(extension); + const extensions: Array> = [ + createExtension({ + namespace: 'test', + attachTo: { id: 'app/routes', input: 'routes' }, + output: [coreExtensionData.reactElement, coreExtensionData.routePath], + factory: () => { + return [ + coreExtensionData.reactElement(element), + coreExtensionData.routePath('/'), + ]; + }, + }), + createRouterExtension({ + namespace: 'test', + Component: ({ children }) => {children}, + }), + ]; if (options?.mountedRoutes) { for (const [path, routeRef] of Object.entries(options.mountedRoutes)) { // TODO(Rugvip): add support for external route refs - tester.add( + extensions.push( createExtension({ kind: 'test-route', name: path, @@ -84,5 +161,17 @@ export function renderInTestApp( ); } } - return tester.render(); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions, + }), + ], + config: ConfigReader.fromConfigs([ + { context: 'render-config', data: options?.config ?? {} }, + ]), + }); + + return render(app.createRoot()); } From d2d4a80937711780ec9b80ea5c4da5655ee979df Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Aug 2024 15:01:58 +0200 Subject: [PATCH 251/393] feat: some more work on splitting out the render method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Co-authored-by: Camila Belo Co-authored-by: Johan Haals Signed-off-by: blam --- .../src/app/createExtensionTester.tsx | 10 +-- .../src/app/renderInTestApp.tsx | 69 ++++++++++--------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 2d842062fc..fb05270eb8 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { MemoryRouter, Link } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom'; import { RenderResult, render } from '@testing-library/react'; import { createSpecializedApp } from '@backstage/frontend-app-api'; import { @@ -24,15 +24,10 @@ import { Extension, ExtensionDataRef, ExtensionDefinition, - IconComponent, - RouteRef, coreExtensionData, createExtension, - createExtensionInput, createExtensionOverrides, - createNavItemExtension, createRouterExtension, - useRouteRef, } from '@backstage/frontend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; @@ -215,14 +210,12 @@ export class ExtensionTester { */ render(options?: { config?: JsonObject }): RenderResult { const { config = {} } = options ?? {}; - const [subject] = this.#extensions; if (!subject) { throw new Error( 'No subject found. At least one extension should be added to the tester.', ); } - const app = createSpecializedApp({ features: [ createExtensionOverrides({ @@ -239,7 +232,6 @@ export class ExtensionTester { ], config: this.#getConfig(config), }); - return render(app.createRoot()); } diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index d828c89f12..85cf1c626c 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -15,25 +15,23 @@ */ import React from 'react'; -import { - ExtensionDefinition, - IconComponent, - RouteRef, - coreExtensionData, - createExtension, - createExtensionInput, - createExtensionOverrides, - createNavItemExtension, - createRouterExtension, - useRouteRef, -} from '@backstage/frontend-plugin-api'; import { Link, MemoryRouter } from 'react-router-dom'; import { createSpecializedApp } from '@backstage/frontend-app-api'; import { render } from '@testing-library/react'; -import { resolveExtensionDefinition } from '@backstage/frontend-plugin-api/src/wiring/resolveExtensionDefinition'; -import { resolve } from 'path'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; +import { + createExtension, + createExtensionOverrides, + createRouterExtension, + ExtensionDefinition, + coreExtensionData, + RouteRef, + useRouteRef, + createExtensionInput, + IconComponent, + createNavItemExtension, +} from '@backstage/frontend-plugin-api'; /** * Options to customize the behavior of the test app. @@ -120,26 +118,35 @@ const TestAppNavExtension = createExtension({ * Renders the given element in a test app, for use in unit tests. */ export function renderInTestApp( - element: JSX.Element, + element: JSX.Element | { extensions: ExtensionDefinition[] }, options?: TestAppOptions, ) { - const extensions: Array> = [ - createExtension({ - namespace: 'test', - attachTo: { id: 'app/routes', input: 'routes' }, - output: [coreExtensionData.reactElement, coreExtensionData.routePath], - factory: () => { - return [ - coreExtensionData.reactElement(element), - coreExtensionData.routePath('/'), + const extensions: Array> = + 'extensions' in element + ? element.extensions + : [ + createExtension({ + namespace: 'test', + attachTo: { id: 'app/routes', input: 'routes' }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + ], + factory: () => { + return [ + coreExtensionData.reactElement(element), + coreExtensionData.routePath('/'), + ]; + }, + }), + createRouterExtension({ + namespace: 'test', + Component: ({ children }) => ( + {children} + ), + }), + TestAppNavExtension, ]; - }, - }), - createRouterExtension({ - namespace: 'test', - Component: ({ children }) => {children}, - }), - ]; if (options?.mountedRoutes) { for (const [path, routeRef] of Object.entries(options.mountedRoutes)) { From 1d6cf554c635fcc4a85df9756e65ddaa473079ac Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Aug 2024 15:11:53 +0200 Subject: [PATCH 252/393] feat: added api-reports Signed-off-by: blam --- packages/frontend-test-utils/api-report.md | 8 +- .../src/app/renderInTestApp.tsx | 86 +++++++++---------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index 3aa9069219..2c16028dd1 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -27,6 +27,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { TestApiProvider } from '@backstage/test-utils'; import { TestApiProviderProps } from '@backstage/test-utils'; import { TestApiRegistry } from '@backstage/test-utils'; +import { testingLibraryDomTypesQueries } from '@testing-library/dom/types/queries'; import { withLogCollector } from '@backstage/test-utils'; // @public (undocumented) @@ -62,8 +63,10 @@ export class ExtensionTester { // (undocumented) data(ref: ExtensionDataRef): T | undefined; // (undocumented) - query(id: string | ExtensionDefinition): ExtensionQuery; + element(): JSX.Element; // (undocumented) + query(id: string | ExtensionDefinition): ExtensionQuery; + // @deprecated (undocumented) render(options?: { config?: JsonObject }): RenderResult; } @@ -97,7 +100,7 @@ export { registerMswTestHooks }; export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, -): RenderResult; +): RenderResult; // @public @deprecated (undocumented) export function setupRequestMockHandlers(worker: { @@ -117,6 +120,7 @@ export type TestAppOptions = { mountedRoutes?: { [path: string]: RouteRef; }; + config?: JsonObject; }; export { withLogCollector }; diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 85cf1c626c..73a29ce0bb 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -23,7 +23,6 @@ import { JsonObject } from '@backstage/types'; import { createExtension, createExtensionOverrides, - createRouterExtension, ExtensionDefinition, coreExtensionData, RouteRef, @@ -31,6 +30,7 @@ import { createExtensionInput, IconComponent, createNavItemExtension, + RouterBlueprint, } from '@backstage/frontend-plugin-api'; /** @@ -86,30 +86,32 @@ const TestAppNavExtension = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, + items: createExtensionInput([createNavItemExtension.targetDataRef]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, }); @@ -118,35 +120,29 @@ const TestAppNavExtension = createExtension({ * Renders the given element in a test app, for use in unit tests. */ export function renderInTestApp( - element: JSX.Element | { extensions: ExtensionDefinition[] }, + element: JSX.Element, options?: TestAppOptions, ) { - const extensions: Array> = - 'extensions' in element - ? element.extensions - : [ - createExtension({ - namespace: 'test', - attachTo: { id: 'app/routes', input: 'routes' }, - output: [ - coreExtensionData.reactElement, - coreExtensionData.routePath, - ], - factory: () => { - return [ - coreExtensionData.reactElement(element), - coreExtensionData.routePath('/'), - ]; - }, - }), - createRouterExtension({ - namespace: 'test', - Component: ({ children }) => ( - {children} - ), - }), - TestAppNavExtension, + const extensions: Array> = [ + createExtension({ + namespace: 'test', + attachTo: { id: 'app/routes', input: 'routes' }, + output: [coreExtensionData.reactElement, coreExtensionData.routePath], + factory: () => { + return [ + coreExtensionData.reactElement(element), + coreExtensionData.routePath('/'), ]; + }, + }), + RouterBlueprint.make({ + namespace: 'test', + params: { + Component: ({ children }) => {children}, + }, + }), + TestAppNavExtension, + ]; if (options?.mountedRoutes) { for (const [path, routeRef] of Object.entries(options.mountedRoutes)) { From c00e1a00f9beb85af31b92ca9f749c2a91a9d629 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Aug 2024 15:20:12 +0200 Subject: [PATCH 253/393] chore: added changeset Signed-off-by: blam --- .changeset/three-kiwis-turn.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/three-kiwis-turn.md diff --git a/.changeset/three-kiwis-turn.md b/.changeset/three-kiwis-turn.md new file mode 100644 index 0000000000..25b5e0497b --- /dev/null +++ b/.changeset/three-kiwis-turn.md @@ -0,0 +1,17 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +--- + +Deprecate the `.render` method of the `createExtensionTester` in favour of using `renderInTestApp` directly. + +```tsx +import { renderInTestApp, createExtensionTester } from '@backstage/frontend-test-utils'; + +const tester = createExtensionTester(extension); + +const { getByTestId } = renderInTestApp(tester.element()); + +// or if you're not using `coreExtensionData.reactElement` as the output ref +const { getByTestId } = renderInTestApp(tester.data(myComponentRef))' +``` From 471473f3911a5c90c5936e1e1fa9fec5b5dbda09 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Aug 2024 15:23:50 +0200 Subject: [PATCH 254/393] chore: ext Signed-off-by: blam Signed-off-by: blam --- .changeset/three-kiwis-turn.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/three-kiwis-turn.md b/.changeset/three-kiwis-turn.md index 25b5e0497b..48a1ffff33 100644 --- a/.changeset/three-kiwis-turn.md +++ b/.changeset/three-kiwis-turn.md @@ -1,5 +1,4 @@ --- -'@backstage/frontend-plugin-api': patch '@backstage/frontend-test-utils': patch --- From 9eec064aaf317efb9df177fdf4701e8b21047ded Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Aug 2024 15:31:23 +0200 Subject: [PATCH 255/393] chore: small refactor Signed-off-by: blam --- .changeset/three-kiwis-turn.md | 7 +++++-- .../src/blueprints/PageBlueprint.test.tsx | 7 +++++-- packages/frontend-test-utils/api-report.md | 3 +-- .../frontend-test-utils/src/app/createExtensionTester.tsx | 2 ++ packages/frontend-test-utils/src/app/renderInTestApp.tsx | 6 +++--- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.changeset/three-kiwis-turn.md b/.changeset/three-kiwis-turn.md index 48a1ffff33..d5802fa586 100644 --- a/.changeset/three-kiwis-turn.md +++ b/.changeset/three-kiwis-turn.md @@ -5,12 +5,15 @@ Deprecate the `.render` method of the `createExtensionTester` in favour of using `renderInTestApp` directly. ```tsx -import { renderInTestApp, createExtensionTester } from '@backstage/frontend-test-utils'; +import { + renderInTestApp, + createExtensionTester, +} from '@backstage/frontend-test-utils'; const tester = createExtensionTester(extension); const { getByTestId } = renderInTestApp(tester.element()); // or if you're not using `coreExtensionData.reactElement` as the output ref -const { getByTestId } = renderInTestApp(tester.data(myComponentRef))' +const { getByTestId } = renderInTestApp(tester.data(myComponentRef)); ``` diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index c4bcb4b467..d70a426a57 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -16,7 +16,10 @@ import React from 'react'; import { createRouteRef } from '../routing'; import { PageBlueprint } from './PageBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { coreExtensionData, createExtensionBlueprint, @@ -144,7 +147,7 @@ describe('PageBlueprint', () => { CardBlueprint.make({ name: 'card', params: {} }), ); - const { getByTestId, getByText } = tester.render(); + const { getByTestId, getByText } = renderInTestApp(tester.element()); await waitFor(() => expect(getByTestId('card')).toBeInTheDocument()); await waitFor(() => diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index 2c16028dd1..0cbdace03e 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -27,7 +27,6 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { TestApiProvider } from '@backstage/test-utils'; import { TestApiProviderProps } from '@backstage/test-utils'; import { TestApiRegistry } from '@backstage/test-utils'; -import { testingLibraryDomTypesQueries } from '@testing-library/dom/types/queries'; import { withLogCollector } from '@backstage/test-utils'; // @public (undocumented) @@ -100,7 +99,7 @@ export { registerMswTestHooks }; export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, -): RenderResult; +): RenderResult; // @public @deprecated (undocumented) export function setupRequestMockHandlers(worker: { diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index fb05270eb8..83ac2aa600 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -43,6 +43,7 @@ import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveA import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/instantiateAppNodeTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; +import { TestAppNavExtension } from './renderInTestApp'; /** @public */ export class ExtensionQuery { @@ -227,6 +228,7 @@ export class ExtensionTester { {children} ), }), + TestAppNavExtension, ], }), ], diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 73a29ce0bb..dd03cdd2a2 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { Link, MemoryRouter } from 'react-router-dom'; import { createSpecializedApp } from '@backstage/frontend-app-api'; -import { render } from '@testing-library/react'; +import { RenderResult, render } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { @@ -81,7 +81,7 @@ const NavItem = (props: { ); }; -const TestAppNavExtension = createExtension({ +export const TestAppNavExtension = createExtension({ namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, @@ -122,7 +122,7 @@ const TestAppNavExtension = createExtension({ export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, -) { +): RenderResult { const extensions: Array> = [ createExtension({ namespace: 'test', From f1e9c08f978c078692f8fea69f6bd82fc16842fe Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 15:37:09 +0200 Subject: [PATCH 256/393] chore: code review comments Signed-off-by: blam --- .../src/blueprints/NavItemBlueprint.test.tsx | 4 +- .../src/blueprints/NavLogoBlueprint.test.tsx | 2 +- .../src/blueprints/PageBlueprint.test.tsx | 4 +- .../blueprints/SignInPageBlueprint.test.tsx | 2 +- .../src/blueprints/ThemeBlueprint.test.ts | 2 +- .../blueprints/TranslationBlueprint.test.ts | 2 +- .../src/wiring/createExtension.test.ts | 30 +++---- .../src/app/createExtensionTester.test.tsx | 8 +- .../src/app/createExtensionTester.tsx | 78 ++++++++++++++++--- 9 files changed, 95 insertions(+), 37 deletions(-) diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx index 94b9e7b8e6..daa21f88bb 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx @@ -77,7 +77,7 @@ describe('NavItemBlueprint', () => { const tester = createExtensionTester(extension); - expect(tester.data(NavItemBlueprint.dataRefs.target)).toEqual({ + expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({ title: 'TEST', icon: MockIcon, routeRef: mockRouteRef, @@ -97,7 +97,7 @@ describe('NavItemBlueprint', () => { config: { title: 'OVERRIDDEN' }, }); - expect(tester.data(NavItemBlueprint.dataRefs.target)).toEqual({ + expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({ title: 'OVERRIDDEN', icon: MockIcon, routeRef: mockRouteRef, diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx index 4a96475cf5..b976196080 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx @@ -64,7 +64,7 @@ describe('NavLogoBlueprint', () => { const tester = createExtensionTester(extension); - expect(tester.data(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ + expect(tester.get(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ logoFull, logoIcon, }); diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index d70a426a57..dedc5454bd 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -101,7 +101,7 @@ describe('PageBlueprint', () => { // TODO(blam): test for the routePath output doesn't work, due to the the way the test harness works // expect(tester.data(coreExtensionData.routePath)).toBe('/test'); - expect(tester.data(coreExtensionData.routeRef)).toBe(mockRouteRef); + expect(tester.get(coreExtensionData.routeRef)).toBe(mockRouteRef); const { getByTestId } = tester.render(); @@ -147,7 +147,7 @@ describe('PageBlueprint', () => { CardBlueprint.make({ name: 'card', params: {} }), ); - const { getByTestId, getByText } = renderInTestApp(tester.element()); + const { getByTestId, getByText } = renderInTestApp(tester.reactElement()); await waitFor(() => expect(getByTestId('card')).toBeInTheDocument()); await waitFor(() => diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx index 61b23e56f9..064021bba2 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx @@ -62,7 +62,7 @@ describe('SignInPageBlueprint', () => { const tester = createExtensionTester(extension); - const Element = tester.data(SignInPageBlueprint.dataRefs.component)!; + const Element = tester.get(SignInPageBlueprint.dataRefs.component)!; expect(Element).toBeDefined(); diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts index 40323b6a31..e6daf510eb 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts @@ -56,7 +56,7 @@ describe('ThemeBlueprint', () => { const extension = ThemeBlueprint.make({ params: { theme } }); expect( - createExtensionTester(extension).data(ThemeBlueprint.dataRefs.theme), + createExtensionTester(extension).get(ThemeBlueprint.dataRefs.theme), ).toEqual(theme); }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts index dfb918053a..3c7b271322 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts @@ -76,7 +76,7 @@ describe('TranslationBlueprint', () => { }); expect( - createExtensionTester(extension).data( + createExtensionTester(extension).get( TranslationBlueprint.dataRefs.translation, ), ).toBe(messages); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index e33bdcb9a1..5902c5c3e7 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -688,7 +688,7 @@ describe('createExtension', () => { const tester = createExtensionTester(overridden); - expect(tester.data(numberDataRef)).toBe(43); + expect(tester.get(numberDataRef)).toBe(43); }); it('should work functionally with overrides', () => { @@ -722,14 +722,14 @@ describe('createExtension', () => { }, }); - expect(createExtensionTester(overriden).data(stringDataRef)).toBe( + expect(createExtensionTester(overriden).get(stringDataRef)).toBe( 'foo-boom-override-hello', ); expect( createExtensionTester(overriden, { config: { foo: 'hello', bar: 'world' }, - }).data(stringDataRef), + }).get(stringDataRef), ).toBe('foo-hello-override-world'); }); @@ -809,7 +809,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'orig-opt', single: 'orig-single', @@ -836,7 +836,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'opt', single: 'single', @@ -862,7 +862,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'none', single: 'single', @@ -885,7 +885,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'orig-opt', single: 'orig-single', @@ -912,7 +912,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'orig-opt', single: 'orig-single', @@ -939,7 +939,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'orig-opt', single: 'orig-single', @@ -966,7 +966,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'orig-opt', single: 'orig-single', @@ -997,7 +997,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toEqual({ opt: 'none', single: 'override-orig-single', @@ -1023,7 +1023,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toThrowErrorMatchingInlineSnapshot( `"Failed to instantiate extension 'subject', override data provided for input 'multi' must match the length of the original inputs"`, ); @@ -1046,7 +1046,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toThrowErrorMatchingInlineSnapshot( `"Failed to instantiate extension 'subject', override data for input 'multi' may not mix forwarded inputs with data overrides"`, ); @@ -1069,7 +1069,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toThrowErrorMatchingInlineSnapshot( `"Failed to instantiate extension 'subject', missing required extension data value(s) 'test1'"`, ); @@ -1098,7 +1098,7 @@ describe('createExtension', () => { .add(singleExt) .add(multi1Ext) .add(multi2Ext) - .data(outputRef), + .get(outputRef), ).toThrowErrorMatchingInlineSnapshot( `"Failed to instantiate extension 'subject', extension data 'test2' was provided but not declared"`, ); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 889c815fc7..540a986733 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -246,7 +246,7 @@ describe('createExtensionTester', () => { const tester = createExtensionTester(extension); - expect(tester.data(stringDataRef)).toBe('test-text'); + expect(tester.get(stringDataRef)).toBe('test-text'); }); it('should throw an error if trying to access an instance not provided to the tester', () => { @@ -328,8 +328,8 @@ describe('createExtensionTester', () => { const tester = createExtensionTester(extension).add(extension2); - expect(tester.query(extension).data(stringDataRef)).toBe('nest-test-text'); - expect(tester.query(extension2).data(stringDataRef)).toBe('test-text'); + expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); + expect(tester.query(extension2).get(stringDataRef)).toBe('test-text'); // @ts-expect-error expect(tester.query(extension).input('input').data(stringDataRef)).toBe( 'nest-test-text', @@ -362,6 +362,6 @@ describe('createExtensionTester', () => { inputs: { input: 'test-text' }, }); - expect(tester.query(extension).data(stringDataRef)).toBe('nest-test-text'); + expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); }); }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 83ac2aa600..1ca65478dc 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, Link } from 'react-router-dom'; import { RenderResult, render } from '@testing-library/react'; import { createSpecializedApp } from '@backstage/frontend-app-api'; import { @@ -24,10 +24,15 @@ import { Extension, ExtensionDataRef, ExtensionDefinition, + IconComponent, + RouteRef, coreExtensionData, createExtension, + createExtensionInput, createExtensionOverrides, + createNavItemExtension, createRouterExtension, + useRouteRef, } from '@backstage/frontend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; @@ -43,7 +48,57 @@ import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveA import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/instantiateAppNodeTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; -import { TestAppNavExtension } from './renderInTestApp'; + +const NavItem = (props: { + routeRef: RouteRef; + title: string; + icon: IconComponent; +}) => { + const { routeRef, title, icon: Icon } = props; + const link = useRouteRef(routeRef); + if (!link) { + return null; + } + return ( +
  • + + {title} + +
  • + ); +}; + +const TestAppNavExtension = createExtension({ + namespace: 'app', + name: 'nav', + attachTo: { id: 'app/layout', input: 'nav' }, + inputs: { + items: createExtensionInput({ + target: createNavItemExtension.targetDataRef, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( + + ), + }; + }, +}); /** @public */ export class ExtensionQuery { @@ -69,7 +124,7 @@ export class ExtensionQuery { return instance; } - data(ref: ExtensionDataRef): T | undefined { + get(ref: ExtensionDataRef): T | undefined { return this.instance.getData(ref); } } @@ -164,10 +219,10 @@ export class ExtensionTester { return this; } - data(ref: ExtensionDataRef): T | undefined { + get(ref: ExtensionDataRef): T | undefined { const tree = this.#resolveTree(); - return new ExtensionQuery(tree.root).data(ref); + return new ExtensionQuery(tree.root).get(ref); } query(id: string | ExtensionDefinition): ExtensionQuery { @@ -190,16 +245,16 @@ export class ExtensionTester { return new ExtensionQuery(node); } - element(): JSX.Element { + reactElement(): JSX.Element { const tree = this.#resolveTree(); - const element = new ExtensionQuery(tree.root).data( + const element = new ExtensionQuery(tree.root).get( coreExtensionData.reactElement, ); if (!element) { throw new Error( - 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(myComponentDataRef)` method to get the component', + 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(...)` to access output data directly instead', ); } @@ -207,33 +262,36 @@ export class ExtensionTester { } /** - * @deprecated Switch to using `renderInTestApp` directly and using `.element()` or `.get(myComponentDataRef)` to get the component you would like to wrap up + * @deprecated Switch to using `renderInTestApp` directly and using `.reactElement()` or `.get(...)` to get the component you w */ render(options?: { config?: JsonObject }): RenderResult { const { config = {} } = options ?? {}; + const [subject] = this.#extensions; if (!subject) { throw new Error( 'No subject found. At least one extension should be added to the tester.', ); } + const app = createSpecializedApp({ features: [ createExtensionOverrides({ extensions: [ ...this.#extensions.map(extension => extension.definition), + TestAppNavExtension, createRouterExtension({ namespace: 'test', Component: ({ children }) => ( {children} ), }), - TestAppNavExtension, ], }), ], config: this.#getConfig(config), }); + return render(app.createRoot()); } From 52367e47f49784b5b0b8620884ac4eb49f3968b0 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 15:37:46 +0200 Subject: [PATCH 257/393] chore: update changeset Signed-off-by: blam Signed-off-by: blam --- .changeset/three-kiwis-turn.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/three-kiwis-turn.md b/.changeset/three-kiwis-turn.md index d5802fa586..eb67cbbbdc 100644 --- a/.changeset/three-kiwis-turn.md +++ b/.changeset/three-kiwis-turn.md @@ -12,8 +12,8 @@ import { const tester = createExtensionTester(extension); -const { getByTestId } = renderInTestApp(tester.element()); +const { getByTestId } = renderInTestApp(tester.reactElement()); // or if you're not using `coreExtensionData.reactElement` as the output ref -const { getByTestId } = renderInTestApp(tester.data(myComponentRef)); +const { getByTestId } = renderInTestApp(tester.get(myComponentRef)); ``` From 2ffa5d730eafde64eb3c1e7bf1d2bea66a3c7367 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Aug 2024 16:23:38 +0200 Subject: [PATCH 258/393] chore: update api-reports Signed-off-by: blam --- packages/frontend-test-utils/api-report.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index 0cbdace03e..e0f9c164e9 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -43,7 +43,7 @@ export { ErrorWithContext }; export class ExtensionQuery { constructor(node: AppNode); // (undocumented) - data(ref: ExtensionDataRef): T | undefined; + get(ref: ExtensionDataRef): T | undefined; // (undocumented) get instance(): AppNodeInstance; // (undocumented) @@ -60,11 +60,11 @@ export class ExtensionTester { }, ): ExtensionTester; // (undocumented) - data(ref: ExtensionDataRef): T | undefined; - // (undocumented) - element(): JSX.Element; + get(ref: ExtensionDataRef): T | undefined; // (undocumented) query(id: string | ExtensionDefinition): ExtensionQuery; + // (undocumented) + reactElement(): JSX.Element; // @deprecated (undocumented) render(options?: { config?: JsonObject }): RenderResult; } From 1d58ff6425799055d373789303867a3516e4153a Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 13:19:49 +0200 Subject: [PATCH 259/393] chore: update the types a little bit to help with parsing Signed-off-by: blam Signed-off-by: blam --- .../blueprints/SignInPageBlueprint.test.tsx | 2 +- .../src/app/createExtensionTester.test.tsx | 77 ++++++++++++++++++- .../src/app/createExtensionTester.tsx | 46 +++++++---- 3 files changed, 108 insertions(+), 17 deletions(-) diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx index 064021bba2..b45d8ead15 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx @@ -62,7 +62,7 @@ describe('SignInPageBlueprint', () => { const tester = createExtensionTester(extension); - const Element = tester.get(SignInPageBlueprint.dataRefs.component)!; + const Element = tester.get(SignInPageBlueprint.dataRefs.component); expect(Element).toBeDefined(); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 540a986733..13bdca1990 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -240,8 +240,8 @@ describe('createExtensionTester', () => { const extension = createExtension({ namespace: 'test', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension); @@ -297,6 +297,76 @@ describe('createExtensionTester', () => { ); }); + it('should not allow getting extension data for an output that was not defined in the extension', () => { + const internalRef = createExtensionDataRef().with({ + id: 'test.internal', + }); + + const internalRef2 = createExtensionDataRef().with({ + id: 'test.internal2', + }); + + const extension = createExtension({ + namespace: 'test', + name: 'e1', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [stringDataRef, internalRef.optional()], + factory: () => [stringDataRef('test-text')], + }); + + const tester = createExtensionTester(extension); + + const test: string = tester.get(stringDataRef); + + // @ts-expect-error - internalRef is optional + const test2: number = tester.get(internalRef); + + // @ts-expect-error - internalRef2 is not defined in the extension + const test3: number = tester.get(internalRef2); + + expect([test, test2, test3]).toBeDefined(); + }); + + it('should support getting outputs from a query response', () => { + const internalRef = createExtensionDataRef().with({ + id: 'test.internal', + }); + + const internalRef2 = createExtensionDataRef().with({ + id: 'test.internal2', + }); + + const extension = createExtension({ + namespace: 'test', + name: 'e1', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
    bob
    )], + }); + + const extraExtension = createExtension({ + namespace: 'test', + name: 'e1', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [stringDataRef, internalRef.optional()], + factory: () => [stringDataRef('test-text')], + }); + + const tester = createExtensionTester(extension) + .add(extraExtension) + .query(extraExtension); + + const test: string = tester.get(stringDataRef); + + // @ts-expect-error - internalRef is optional + const test2: number = tester.get(internalRef); + + // @ts-expect-error - internalRef2 is not defined in the extension + const test3: number = tester.get(internalRef2); + + expect([test, test2, test3]).toBeDefined(); + }); + // TODO: this should be implemented // eslint-disable-next-line jest/no-disabled-tests it.skip('should allow querying an extension and getting outputs', () => { @@ -328,7 +398,9 @@ describe('createExtensionTester', () => { const tester = createExtensionTester(extension).add(extension2); + // @ts-expect-error expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); + // @ts-expect-error expect(tester.query(extension2).get(stringDataRef)).toBe('test-text'); // @ts-expect-error expect(tester.query(extension).input('input').data(stringDataRef)).toBe( @@ -362,6 +434,7 @@ describe('createExtensionTester', () => { inputs: { input: 'test-text' }, }); + // @ts-expect-error expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); }); }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 1ca65478dc..b3c1b385e3 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -19,6 +19,7 @@ import { MemoryRouter, Link } from 'react-router-dom'; import { RenderResult, render } from '@testing-library/react'; import { createSpecializedApp } from '@backstage/frontend-app-api'; import { + AnyExtensionDataRef, AppNode, AppTree, Extension, @@ -101,7 +102,7 @@ const TestAppNavExtension = createExtension({ }); /** @public */ -export class ExtensionQuery { +export class ExtensionQuery { #node: AppNode; constructor(node: AppNode) { @@ -124,18 +125,24 @@ export class ExtensionQuery { return instance; } - get(ref: ExtensionDataRef): T | undefined { + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never { return this.instance.getData(ref); } } /** @public */ -export class ExtensionTester { +export class ExtensionTester { /** @internal */ - static forSubject( + static forSubject( subject: ExtensionDefinition, options?: { config?: TConfigInput }, - ): ExtensionTester { + ): ExtensionTester { const tester = new ExtensionTester(); const internal = toInternalExtensionDefinition(subject); @@ -192,7 +199,7 @@ export class ExtensionTester { add( extension: ExtensionDefinition, options?: { config?: TConfigInput }, - ): ExtensionTester { + ): ExtensionTester { if (this.#tree) { throw new Error( 'Cannot add more extensions accessing the extension tree', @@ -219,17 +226,24 @@ export class ExtensionTester { return this; } - get(ref: ExtensionDataRef): T | undefined { + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never { const tree = this.#resolveTree(); return new ExtensionQuery(tree.root).get(ref); } - query(id: string | ExtensionDefinition): ExtensionQuery { + query( + extension: ExtensionDefinition, + ): ExtensionQuery { const tree = this.#resolveTree(); - const actualId = - typeof id === 'string' ? id : resolveExtensionDefinition(id).id; + const actualId = resolveExtensionDefinition(extension).id; const node = tree.nodes.get(actualId); @@ -355,9 +369,13 @@ export class ExtensionTester { } /** @public */ -export function createExtensionTester( - subject: ExtensionDefinition, - options?: { config?: TConfig }, -): ExtensionTester { +export function createExtensionTester< + TConfig, + TConfigInput, + UOutput extends AnyExtensionDataRef, +>( + subject: ExtensionDefinition, + options?: { config?: TConfigInput }, +): ExtensionTester { return ExtensionTester.forSubject(subject, options); } From 025b9fe183aab6f62413da657a40164031632fee Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 13:26:15 +0200 Subject: [PATCH 260/393] chore: reflect a data container Signed-off-by: blam Signed-off-by: blam --- packages/frontend-test-utils/api-report.md | 39 ++++++++++++++----- .../blueprints/EntityCardBlueprint.test.tsx | 6 +-- .../EntityContentBlueprint.test.tsx | 12 +++--- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index e0f9c164e9..164c222d84 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -7,6 +7,7 @@ import { AnalyticsApi } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppNodeInstance } from '@backstage/frontend-plugin-api'; import { ErrorWithContext } from '@backstage/test-utils'; @@ -30,20 +31,30 @@ import { TestApiRegistry } from '@backstage/test-utils'; import { withLogCollector } from '@backstage/test-utils'; // @public (undocumented) -export function createExtensionTester( - subject: ExtensionDefinition, +export function createExtensionTester< + TConfig, + TConfigInput, + UOutput extends AnyExtensionDataRef, +>( + subject: ExtensionDefinition, options?: { - config?: TConfig; + config?: TConfigInput; }, -): ExtensionTester; +): ExtensionTester; export { ErrorWithContext }; // @public (undocumented) -export class ExtensionQuery { +export class ExtensionQuery { constructor(node: AppNode); // (undocumented) - get(ref: ExtensionDataRef): T | undefined; + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never; // (undocumented) get instance(): AppNodeInstance; // (undocumented) @@ -51,18 +62,26 @@ export class ExtensionQuery { } // @public (undocumented) -export class ExtensionTester { +export class ExtensionTester { // (undocumented) add( extension: ExtensionDefinition, options?: { config?: TConfigInput; }, - ): ExtensionTester; + ): ExtensionTester; // (undocumented) - get(ref: ExtensionDataRef): T | undefined; + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never; // (undocumented) - query(id: string | ExtensionDefinition): ExtensionQuery; + query( + extension: ExtensionDefinition, + ): ExtensionQuery; // (undocumented) reactElement(): JSX.Element; // @deprecated (undocumented) diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx index db787766fd..0cb7cdead8 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx @@ -100,7 +100,7 @@ describe('EntityCardBlueprint', () => { filter: 'test', }, }), - ).data(EntityCardBlueprint.dataRefs.filterExpression), + ).get(EntityCardBlueprint.dataRefs.filterExpression), ).toBe('test'); expect( @@ -112,7 +112,7 @@ describe('EntityCardBlueprint', () => { }, }), { config: { filter: 'test' } }, - ).data(EntityCardBlueprint.dataRefs.filterExpression), + ).get(EntityCardBlueprint.dataRefs.filterExpression), ).toBe('test'); expect( @@ -124,7 +124,7 @@ describe('EntityCardBlueprint', () => { loader: async () =>
    Test!
    , }, }), - ).data(EntityCardBlueprint.dataRefs.filterFunction), + ).get(EntityCardBlueprint.dataRefs.filterFunction), ).toBe(mockFilter); }); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx index dab6013bab..5d91f402de 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -122,10 +122,10 @@ describe('EntityContentBlueprint', () => { const tester = createExtensionTester(extension); // todo(blam): route paths are always set to / in the createExtensionTester. This will work eventually. - // expect(tester.data(coreExtensionData.routePath)).toBe('/test'); + // expect(tester.get(coreExtensionData.routePath)).toBe('/test'); - expect(tester.data(coreExtensionData.routeRef)).toBe(mockRouteRef); - expect(tester.data(EntityContentBlueprint.dataRefs.title)).toBe('Test'); + expect(tester.get(coreExtensionData.routeRef)).toBe(mockRouteRef); + expect(tester.get(EntityContentBlueprint.dataRefs.title)).toBe('Test'); }); it('should emit the correct filter output', () => { @@ -142,7 +142,7 @@ describe('EntityContentBlueprint', () => { filter: 'test', }, }), - ).data(EntityContentBlueprint.dataRefs.filterExpression), + ).get(EntityContentBlueprint.dataRefs.filterExpression), ).toBe('test'); expect( @@ -156,7 +156,7 @@ describe('EntityContentBlueprint', () => { }, }), { config: { filter: 'test' } }, - ).data(EntityContentBlueprint.dataRefs.filterExpression), + ).get(EntityContentBlueprint.dataRefs.filterExpression), ).toBe('test'); expect( @@ -170,7 +170,7 @@ describe('EntityContentBlueprint', () => { loader: async () =>
    Test!
    , }, }), - ).data(EntityContentBlueprint.dataRefs.filterFunction), + ).get(EntityContentBlueprint.dataRefs.filterFunction), ).toBe(mockFilter); }); From 38f3827e5a21676f384b374d7a8efe4d63736604 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Aug 2024 15:59:16 +0200 Subject: [PATCH 261/393] Remove deprecated UrlReaders and update usages Signed-off-by: Johan Haals --- .../backend-common/src/deprecated/index.ts | 230 ------------------ .../src/manager/types.ts | 8 +- packages/backend-legacy/package.json | 2 + packages/backend-legacy/src/index.ts | 2 +- packages/backend-legacy/src/types.ts | 4 +- .../catalog-backend-module-aws/package.json | 1 + .../AwsS3DiscoveryProcessor.test.ts | 2 +- .../src/processors/AwsS3DiscoveryProcessor.ts | 4 +- .../src/types.ts | 9 +- .../src/OpenApiRefProcessor.ts | 8 +- .../modules/codeowners/CodeOwnersProcessor.ts | 9 +- .../src/modules/codeowners/lib/read.ts | 6 +- .../modules/core/PlaceholderProcessor.test.ts | 4 +- .../src/modules/core/PlaceholderProcessor.ts | 4 +- .../modules/core/UrlReaderProcessor.test.ts | 5 +- .../src/modules/core/UrlReaderProcessor.ts | 5 +- .../src/service/CatalogBuilder.ts | 4 +- .../confluenceToMarkdown.examples.test.ts | 5 +- .../confluence/confluenceToMarkdown.test.ts | 5 +- .../confluence/confluenceToMarkdown.ts | 4 +- .../package.json | 1 + .../fetch/cookiecutter.examples.test.ts | 5 +- .../src/actions/fetch/cookiecutter.test.ts | 5 +- .../src/actions/fetch/cookiecutter.ts | 9 +- .../fetch/rails/index.examples.test.ts | 5 +- .../src/actions/fetch/rails/index.test.ts | 5 +- .../src/actions/fetch/rails/index.ts | 5 +- plugins/scaffolder-backend/package.json | 1 + .../actions/builtin/createBuiltinActions.ts | 7 +- .../builtin/fetch/plain.examples.test.ts | 4 +- .../actions/builtin/fetch/plain.test.ts | 4 +- .../scaffolder/actions/builtin/fetch/plain.ts | 8 +- .../builtin/fetch/plainFile.examples.test.ts | 4 +- .../actions/builtin/fetch/plainFile.test.ts | 4 +- .../actions/builtin/fetch/plainFile.ts | 4 +- .../builtin/fetch/template.examples.test.ts | 4 +- .../actions/builtin/fetch/template.test.ts | 8 +- .../actions/builtin/fetch/template.ts | 4 +- .../src/service/router.test.ts | 2 +- .../scaffolder-backend/src/service/router.ts | 4 +- .../scaffolder-node/src/actions/fetch.test.ts | 4 +- plugins/scaffolder-node/src/actions/fetch.ts | 6 +- plugins/techdocs-node/src/helpers.ts | 4 +- .../src/stages/prepare/dir.test.ts | 5 +- .../techdocs-node/src/stages/prepare/dir.ts | 6 +- .../techdocs-node/src/stages/prepare/types.ts | 4 +- .../techdocs-node/src/stages/prepare/url.ts | 6 +- yarn.lock | 4 + 48 files changed, 122 insertions(+), 331 deletions(-) diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index e3e4101d25..5dfcd322ed 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -36,45 +36,6 @@ import { type LegacyRootDatabaseService as _LegacyRootDatabaseService, } from '../../../backend-defaults/src/entrypoints/database/DatabaseManager'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AzureUrlReader as _AzureUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/AzureUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { BitbucketCloudUrlReader as _BitbucketCloudUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { BitbucketUrlReader as _BitbucketUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { BitbucketServerUrlReader as _BitbucketServerUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/BitbucketServerUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GerritUrlReader as _GerritUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GithubUrlReader as _GithubUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GitlabUrlReader as _GitlabUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GiteaUrlReader as _GiteaUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GiteaUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { HarnessUrlReader as _HarnessUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/HarnessUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AwsS3UrlReader as _AwsS3UrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { FetchUrlReader as _FetchUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/FetchUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { UrlReaders as _UrlReaders } from '../../../backend-defaults/src/entrypoints/urlReader/lib/UrlReaders'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { ReadUrlResponseFactory as _ReadUrlResponseFactory } from '../../../backend-defaults/src/entrypoints/urlReader/lib/ReadUrlResponseFactory'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import type { UrlReadersOptions as _UrlReadersOptions } from '../../../backend-defaults/src/entrypoints/urlReader/lib/UrlReaders'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import type { FromReadableArrayOptions as _FromReadableArrayOptions } from '../../../backend-defaults/src/entrypoints/urlReader/lib/types'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import type { - ReaderFactory as _ReaderFactory, - ReadTreeResponseFactory as _ReadTreeResponseFactory, - ReadTreeResponseFactoryOptions as _ReadTreeResponseFactoryOptions, - ReadUrlResponseFactoryFromStreamOptions as _ReadUrlResponseFactoryFromStreamOptions, - UrlReaderPredicateTuple as _UrlReaderPredicateTuple, -} from '../../../backend-defaults/src/entrypoints/urlReader/lib/types'; - import { DiscoveryService, CacheService, @@ -85,16 +46,6 @@ import { resolvePackagePath as _resolvePackagePath, resolveSafeChildPath as _resolveSafeChildPath, isChildPath as _isChildPath, - ReadTreeOptions as _ReadTreeOptions, - ReadTreeResponse as _ReadTreeResponse, - ReadTreeResponseFile as _ReadTreeResponseFile, - ReadTreeResponseDirOptions as _ReadTreeResponseDirOptions, - ReadUrlOptions as _ReadUrlOptions, - ReadUrlResponse as _ReadUrlResponse, - SearchOptions as _SearchOptions, - SearchResponse as _SearchResponse, - SearchResponseFile as _SearchResponseFile, - UrlReaderService as _UrlReaderService, LifecycleService, PluginMetadataService, } from '@backstage/backend-plugin-api'; @@ -290,184 +241,3 @@ export const resolveSafeChildPath = _resolveSafeChildPath; * Please use the `isChildPath` function from the `@backstage/cli-common` package instead. */ export const isChildPath = _isChildPath; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class AzureUrlReader extends _AzureUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class BitbucketCloudUrlReader extends _BitbucketCloudUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class BitbucketUrlReader extends _BitbucketUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class BitbucketServerUrlReader extends _BitbucketServerUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GerritUrlReader extends _GerritUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GithubUrlReader extends _GithubUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GitlabUrlReader extends _GitlabUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GiteaUrlReader extends _GiteaUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class HarnessUrlReader extends _HarnessUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class AwsS3UrlReader extends _AwsS3UrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class FetchUrlReader extends _FetchUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class UrlReaders extends _UrlReaders {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class ReadUrlResponseFactory extends _ReadUrlResponseFactory {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type UrlReadersOptions = _UrlReadersOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type FromReadableArrayOptions = _FromReadableArrayOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReaderFactory = _ReaderFactory; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReadTreeResponseFactory = _ReadTreeResponseFactory; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReadTreeResponseFactoryOptions = _ReadTreeResponseFactoryOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReadUrlResponseFactoryFromStreamOptions = - _ReadUrlResponseFactoryFromStreamOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type UrlReaderPredicateTuple = _UrlReaderPredicateTuple; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeOptions = _ReadTreeOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponse = _ReadTreeResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponseFile = _ReadTreeResponseFile; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponseDirOptions = _ReadTreeResponseDirOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadUrlOptions = _ReadUrlOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead - */ -export type ReadUrlResponse = _ReadUrlResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead - */ -export type SearchOptions = _SearchOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead - */ -export type SearchResponse = _SearchResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead - */ -export type SearchResponseFile = _SearchResponseFile; - -/** - * @public - * @deprecated Use `UrlReaderService` from `@backstage/backend-plugin-api` instead - */ -export type UrlReader = _UrlReaderService; diff --git a/packages/backend-dynamic-feature-service/src/manager/types.ts b/packages/backend-dynamic-feature-service/src/manager/types.ts index 60b2eb831e..0234a8b66e 100644 --- a/packages/backend-dynamic-feature-service/src/manager/types.ts +++ b/packages/backend-dynamic-feature-service/src/manager/types.ts @@ -21,7 +21,6 @@ import { PluginDatabaseManager, PluginEndpointDiscovery, TokenManager, - UrlReader, } from '@backstage/backend-common'; import { Router } from 'express'; import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; @@ -33,7 +32,10 @@ import { HttpPostIngressOptions, } from '@backstage/plugin-events-node'; -import { BackendFeature } from '@backstage/backend-plugin-api'; +import { + BackendFeature, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { PackagePlatform, PackageRole } from '@backstage/cli-node'; import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -58,7 +60,7 @@ export type LegacyPluginEnvironment = { cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 7e01d94dd7..4fee66d925 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -29,6 +29,8 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/packages/backend-legacy/src/index.ts b/packages/backend-legacy/src/index.ts index 33520cba03..e422a67267 100644 --- a/packages/backend-legacy/src/index.ts +++ b/packages/backend-legacy/src/index.ts @@ -32,7 +32,6 @@ import { loadBackendConfig, notFoundHandler, ServerTokenManager, - UrlReaders, useHotMemoize, } from '@backstage/backend-common'; import { TaskScheduler } from '@backstage/backend-tasks'; @@ -57,6 +56,7 @@ import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; import { DefaultEventBroker } from '@backstage/plugin-events-backend'; import { DefaultEventsService } from '@backstage/plugin-events-node'; import { DefaultSignalsService } from '@backstage/plugin-signals-node'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; function makeCreateEnv(config: Config) { const root = getRootLogger(); diff --git a/packages/backend-legacy/src/types.ts b/packages/backend-legacy/src/types.ts index a75b1620c4..b2c0f58ee9 100644 --- a/packages/backend-legacy/src/types.ts +++ b/packages/backend-legacy/src/types.ts @@ -21,20 +21,20 @@ import { PluginDatabaseManager, PluginEndpointDiscovery, TokenManager, - UrlReader, } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { SignalsService } from '@backstage/plugin-signals-node'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; export type PluginEnvironment = { logger: Logger; cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 1313cc0dd5..deff27597b 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -58,6 +58,7 @@ "@aws-sdk/middleware-endpoint": "^3.347.0", "@aws-sdk/types": "^3.347.0", "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts index 804a17f4f0..70b4ddf2d4 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReaders } from '@backstage/backend-common'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; import { ConfigReader } from '@backstage/config'; import { AwsS3DiscoveryProcessor } from './AwsS3DiscoveryProcessor'; import { diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts index d6722d046e..dfff1f5d4f 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { isError } from '@backstage/errors'; import { CatalogProcessor, @@ -34,7 +34,7 @@ import limiterFactory from 'p-limit'; * @deprecated Use the `AwsS3EntityProvider` instead (see https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-aws/CHANGELOG.md#014). */ export class AwsS3DiscoveryProcessor implements CatalogProcessor { - constructor(private readonly reader: UrlReader) {} + constructor(private readonly reader: UrlReaderService) {} getProcessorName(): string { return 'AwsS3DiscoveryProcessor'; diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts index 78309afb36..7c637de841 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import type { - PluginDatabaseManager, - UrlReader, -} from '@backstage/backend-common'; +import type { PluginDatabaseManager } from '@backstage/backend-common'; import type { PluginTaskScheduler, TaskFunction, @@ -32,7 +29,7 @@ import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import type { DurationObjectUnits } from 'luxon'; import type { Logger } from 'winston'; import { IncrementalIngestionDatabaseManager } from './database/IncrementalIngestionDatabaseManager'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; /** * Ingest entities into the catalog in bite-sized chunks. @@ -192,7 +189,7 @@ export type PluginEnvironment = { database: PluginDatabaseManager; scheduler: PluginTaskScheduler; config: Config; - reader: UrlReader; + reader: UrlReaderService; permissions: PermissionEvaluator; }; diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts index 6b00519b2f..39de58466e 100644 --- a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -29,11 +29,11 @@ import { Logger } from 'winston'; export class OpenApiRefProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrations; private readonly logger: Logger; - private readonly reader: UrlReader; + private readonly reader: UrlReaderService; static fromConfig( config: Config, - options: { logger: Logger; reader: UrlReader }, + options: { logger: Logger; reader: UrlReaderService }, ) { const integrations = ScmIntegrations.fromConfig(config); @@ -46,7 +46,7 @@ export class OpenApiRefProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrations; logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }) { this.integrations = options.integrations; this.logger = options.logger; diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts index b4ed33d162..9b8d2a3bbf 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -24,7 +23,7 @@ import { import { LocationSpec } from '@backstage/plugin-catalog-common'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { findCodeOwnerByTarget } from './lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; const ALLOWED_KINDS = ['API', 'Component', 'Domain', 'Resource', 'System']; const ALLOWED_LOCATION_TYPES = ['url']; @@ -33,11 +32,11 @@ const ALLOWED_LOCATION_TYPES = ['url']; export class CodeOwnersProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly logger: LoggerService; - private readonly reader: UrlReader; + private readonly reader: UrlReaderService; static fromConfig( config: Config, - options: { logger: LoggerService; reader: UrlReader }, + options: { logger: LoggerService; reader: UrlReaderService }, ) { const integrations = ScmIntegrations.fromConfig(config); @@ -50,7 +49,7 @@ export class CodeOwnersProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry; logger: LoggerService; - reader: UrlReader; + reader: UrlReaderService; }) { this.integrations = options.integrations; this.logger = options.logger; diff --git a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts index 6a5234c94f..155405ddf8 100644 --- a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts +++ b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { NotFoundError } from '@backstage/errors'; import { ScmIntegration } from '@backstage/integration'; import 'core-js/features/promise'; // NOTE: This can be removed when ES2021 is implemented import { resolveCodeOwner } from './resolve'; import { scmCodeOwnersPaths } from './scm'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; export async function readCodeOwners( - reader: UrlReader, + reader: UrlReaderService, sourceUrl: string, codeownersPaths: string[], ): Promise { @@ -49,7 +49,7 @@ export async function readCodeOwners( } export async function findCodeOwnerByTarget( - reader: UrlReader, + reader: UrlReaderService, targetUrl: string, scmIntegration: ScmIntegration, ): Promise { diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts index 094f8f1df8..c2c49ea1cb 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -29,11 +28,12 @@ import { textPlaceholderResolver, yamlPlaceholderResolver, } from './PlaceholderProcessor'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); describe('PlaceholderProcessor', () => { - const reader: jest.Mocked = { + const reader: jest.Mocked = { readTree: jest.fn(), search: jest.fn(), readUrl: jest.fn(), diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts index f6431cc9f5..bdbb0433d7 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -27,11 +26,12 @@ import { PlaceholderResolverParams, processingResult, } from '@backstage/plugin-catalog-node'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; /** @public */ export type PlaceholderProcessorOptions = { resolvers: Record; - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrationRegistry; }; diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index 7d1578db00..7e895c5344 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader, UrlReaders } from '@backstage/backend-common'; import { mockServices, registerMswTestHooks, @@ -31,6 +30,8 @@ import { } from '@backstage/plugin-catalog-node'; import { defaultEntityDataParser } from '../util/parse'; import { UrlReaderProcessor } from './UrlReaderProcessor'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('UrlReaderProcessor', () => { const mockApiOrigin = 'http://localhost'; @@ -192,7 +193,7 @@ describe('UrlReaderProcessor', () => { it('uses search when there are globs', async () => { const logger = mockServices.logger.mock(); - const reader: jest.Mocked = { + const reader: jest.Mocked = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn().mockImplementation(async () => []), diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 89f9e74685..b623098171 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; import limiterFactory from 'p-limit'; @@ -29,7 +28,7 @@ import { CatalogProcessorResult, processingResult, } from '@backstage/plugin-catalog-node'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; const CACHE_KEY = 'v1'; @@ -47,7 +46,7 @@ type CacheItem = { export class UrlReaderProcessor implements CatalogProcessor { constructor( private readonly options: { - reader: UrlReader; + reader: UrlReaderService; logger: LoggerService; }, ) {} diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index aabef51195..344c231f7c 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -18,7 +18,6 @@ import { createLegacyAuthAdapters, HostDiscovery, PluginDatabaseManager, - UrlReader, } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { @@ -112,6 +111,7 @@ import { HttpAuthService, LoggerService, PermissionsService, + UrlReaderService, } from '@backstage/backend-plugin-api'; /** @@ -128,7 +128,7 @@ export type CatalogEnvironment = { logger: LoggerService; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; scheduler?: PluginTaskScheduler; discovery?: DiscoveryService; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts index 36ae750723..5cf8969b55 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { createConfluenceToMarkdownAction } from './confluenceToMarkdown'; -import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { @@ -28,6 +28,7 @@ import { examples } from './confluenceToMarkdown.examples'; import yaml from 'yaml'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('confluence:transform:markdown examples', () => { const baseUrl = `https://confluence.example.com`; @@ -51,7 +52,7 @@ describe('confluence:transform:markdown examples', () => { }), ); - let reader: UrlReader; + let reader: UrlReaderService; let mockContext: ActionContext<{ confluenceUrls: string[]; repoUrl: string; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts index 4c3c6b891f..a54e5dcc63 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts @@ -15,7 +15,7 @@ */ import { createConfluenceToMarkdownAction } from './confluenceToMarkdown'; -import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { @@ -27,6 +27,7 @@ import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('confluence:transform:markdown', () => { const baseUrl = `https://nodomain.confluence.com`; @@ -50,7 +51,7 @@ describe('confluence:transform:markdown', () => { }), ); - let reader: UrlReader; + let reader: UrlReaderService; let mockContext: ActionContext<{ confluenceUrls: string[]; repoUrl: string; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index e88f42e39c..aa9f94988a 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -15,7 +15,6 @@ */ import { Config } from '@backstage/config'; -import { UrlReader } from '@backstage/backend-common'; import { ScmIntegrations } from '@backstage/integration'; import { createTemplateAction, @@ -34,13 +33,14 @@ import { getConfluenceConfig, } from './helpers'; import { examples } from './confluenceToMarkdown.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; /** * @public */ export const createConfluenceToMarkdownAction = (options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; config: Config; }) => { diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 6029ed02b4..f412ff11b1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -44,6 +44,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts index ae46ef1bd6..73c17d8ac0 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader, ContainerRunner } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; @@ -25,6 +25,7 @@ import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { examples } from './cookiecutter.examples'; import yaml from 'yaml'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; const executeShellCommand = jest.fn(); const commandExists = jest.fn(); @@ -71,7 +72,7 @@ describe('fetch:cookiecutter', () => { runContainer: jest.fn(), }; - const mockReader: UrlReader = { + const mockReader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index fad544488a..52946ce7fd 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader, ContainerRunner } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; @@ -24,6 +24,7 @@ import { join } from 'path'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Writable } from 'stream'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; const executeShellCommand = jest.fn(); const commandExists = jest.fn(); @@ -70,7 +71,7 @@ describe('fetch:cookiecutter', () => { runContainer: jest.fn(), }; - const mockReader: UrlReader = { + const mockReader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index efbe0a07cf..6e540d7fca 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { ContainerRunner } from '@backstage/backend-common'; +import { + UrlReaderService, + resolveSafeChildPath, +} from '@backstage/backend-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -140,7 +143,7 @@ export class CookiecutterRunner { * @public */ export function createFetchCookiecutterAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; }) { diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts index 37e678ab6a..6bc84e04ad 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts @@ -27,7 +27,7 @@ jest.mock('./railsNewRunner', () => { }; }); -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { resolve as resolvePath } from 'path'; @@ -37,6 +37,7 @@ import { createMockDirectory } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { examples } from './index.examples'; import yaml from 'yaml'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:rails', () => { const mockDir = createMockDirectory(); @@ -66,7 +67,7 @@ describe('fetch:rails', () => { workspacePath: mockDir.path, }); - const mockReader: UrlReader = { + const mockReader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index 40514dc7c5..616c90e0de 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -27,7 +27,7 @@ jest.mock('./railsNewRunner', () => { }; }); -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { resolve as resolvePath } from 'path'; @@ -36,6 +36,7 @@ import { fetchContents } from '@backstage/plugin-scaffolder-node'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Writable } from 'stream'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:rails', () => { const mockDir = createMockDirectory(); @@ -65,7 +66,7 @@ describe('fetch:rails', () => { workspacePath: mockDir.path, }); - const mockReader: UrlReader = { + const mockReader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts index 94a2a3539b..4d5db0f3ef 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -28,6 +28,7 @@ import { resolve as resolvePath } from 'path'; import { RailsNewRunner } from './railsNewRunner'; import { PassThrough } from 'stream'; import { examples } from './index.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; /** * Creates the `fetch:rails` Scaffolder action. @@ -40,7 +41,7 @@ import { examples } from './index.examples'; * @public */ export function createFetchRailsAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; /** A list of image names that are allowed to be passed as imageName input */ diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a8413f9931..3a61e3d464 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 673776bec9..dba867c96b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { @@ -82,7 +81,7 @@ import { } from '@backstage/plugin-scaffolder-backend-module-gitlab'; import { createPublishGiteaAction } from '@backstage/plugin-scaffolder-backend-module-gitea'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { AuthService, UrlReaderService } from '@backstage/backend-plugin-api'; /** * The options passed to {@link createBuiltinActions} @@ -90,9 +89,9 @@ import { AuthService } from '@backstage/backend-plugin-api'; */ export interface CreateBuiltInActionsOptions { /** - * The {@link @backstage/backend-common#UrlReader} interface that will be used in the default actions. + * The {@link @backstage/backend-plugin-api#UrlReaderService} interface that will be used in the default actions. */ - reader: UrlReader; + reader: UrlReaderService; /** * The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions. */ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts index 82a32fcbac..9dbb1f9a03 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts @@ -17,13 +17,13 @@ import yaml from 'yaml'; import { resolve as resolvePath } from 'path'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchPlainAction } from './plain'; import { fetchContents } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { examples } from './plain.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; jest.mock('@backstage/plugin-scaffolder-node', () => ({ ...jest.requireActual('@backstage/plugin-scaffolder-node'), @@ -38,7 +38,7 @@ describe('fetch:plain examples', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index bc20fafe19..8a52ef7392 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -21,11 +21,11 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { resolve as resolvePath } from 'path'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchContents } from '@backstage/plugin-scaffolder-node'; import { createFetchPlainAction } from './plain'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:plain', () => { const integrations = ScmIntegrations.fromConfig( @@ -35,7 +35,7 @@ describe('fetch:plain', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index c6beaa2d67..a4f795dd54 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { + resolveSafeChildPath, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plain.examples'; @@ -32,7 +34,7 @@ export const ACTION_ID = 'fetch:plain'; * @public */ export function createFetchPlainAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }) { const { reader, integrations } = options; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts index 9978d6d8f5..a091a6ecfe 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts @@ -23,12 +23,12 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import yaml from 'yaml'; import { resolve as resolvePath } from 'path'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchPlainFileAction } from './plainFile'; import { fetchFile } from '@backstage/plugin-scaffolder-node'; import { examples } from './plainFile.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:plain:file examples', () => { const integrations = ScmIntegrations.fromConfig( @@ -38,7 +38,7 @@ describe('fetch:plain:file examples', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts index bf40c7ad0e..637a209bd9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts @@ -21,11 +21,11 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { resolve as resolvePath } from 'path'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchFile } from '@backstage/plugin-scaffolder-node'; import { createFetchPlainFileAction } from './plainFile'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:plain:file', () => { const integrations = ScmIntegrations.fromConfig( @@ -35,7 +35,7 @@ describe('fetch:plain:file', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts index 92db7aa3ed..a07c9bfc6b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plainFile.examples'; @@ -29,7 +29,7 @@ import { * @public */ export function createFetchPlainFileAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }) { const { reader, integrations } = options; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts index 02fe0c30a4..41ce875317 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts @@ -16,7 +16,7 @@ import { join as joinPath, sep as pathSep } from 'path'; import fs from 'fs-extra'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -71,7 +71,7 @@ describe('fetch:template examples', () => { beforeEach(() => { mockDir.clear(); action = createFetchTemplateAction({ - reader: Symbol('UrlReader') as unknown as UrlReader, + reader: Symbol('UrlReader') as unknown as UrlReaderService, integrations: Symbol('Integrations') as unknown as ScmIntegrations, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 0542b6a50d..87a6c84750 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -21,8 +21,10 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { join as joinPath, sep as pathSep } from 'path'; import fs from 'fs-extra'; -import { UrlReader } from '@backstage/backend-common'; -import { resolvePackagePath } from '@backstage/backend-plugin-api'; +import { + UrlReaderService, + resolvePackagePath, +} from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchTemplateAction } from './template'; import { @@ -78,7 +80,7 @@ describe('fetch:template', () => { workspace: {}, }); action = createFetchTemplateAction({ - reader: Symbol('UrlReader') as unknown as UrlReader, + reader: Symbol('UrlReaderService') as unknown as UrlReaderService, integrations: Symbol('Integrations') as unknown as ScmIntegrations, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 0983199339..2025202c22 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -15,7 +15,7 @@ */ import { extname } from 'path'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -40,7 +40,7 @@ import { examples } from './template.examples'; * @public */ export function createFetchTemplateAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index e53f3a5dd9..51e11d630f 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -18,7 +18,6 @@ import { DatabaseManager, loggerToWinstonLogger, PluginDatabaseManager, - UrlReaders, } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { ConfigReader } from '@backstage/config'; @@ -48,6 +47,7 @@ import { import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha'; import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; const mockAccess = jest.fn(); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index a25731ce45..7dcf702bf7 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -18,7 +18,6 @@ import { createLegacyAuthAdapters, HostDiscovery, PluginDatabaseManager, - UrlReader, } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { CatalogApi } from '@backstage/catalog-client'; @@ -88,6 +87,7 @@ import { HttpAuthService, LifecycleService, PermissionsService, + UrlReaderService, } from '@backstage/backend-plugin-api'; import { IdentityApi, @@ -144,7 +144,7 @@ function isActionPermissionRuleInput( export interface RouterOptions { logger: Logger; config: Config; - reader: UrlReader; + reader: UrlReaderService; lifecycle?: LifecycleService; database: PluginDatabaseManager; catalogClient: CatalogApi; diff --git a/plugins/scaffolder-node/src/actions/fetch.test.ts b/plugins/scaffolder-node/src/actions/fetch.test.ts index ea7f458fde..f831d20f35 100644 --- a/plugins/scaffolder-node/src/actions/fetch.test.ts +++ b/plugins/scaffolder-node/src/actions/fetch.test.ts @@ -18,7 +18,7 @@ jest.mock('fs-extra'); import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchContents, fetchFile } from './fetch'; @@ -39,7 +39,7 @@ describe('fetchContents helper', () => { const readUrl = jest.fn(); const readTree = jest.fn(); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl, readTree, search: jest.fn(), diff --git a/plugins/scaffolder-node/src/actions/fetch.ts b/plugins/scaffolder-node/src/actions/fetch.ts index 63bc4b1352..a0e8dd9f85 100644 --- a/plugins/scaffolder-node/src/actions/fetch.ts +++ b/plugins/scaffolder-node/src/actions/fetch.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -28,7 +28,7 @@ import path from 'path'; * @public */ export async function fetchContents(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; @@ -67,7 +67,7 @@ export async function fetchContents(options: { * @public */ export async function fetchFile(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; diff --git a/plugins/techdocs-node/src/helpers.ts b/plugins/techdocs-node/src/helpers.ts index d64c7fc289..38913e8e9b 100644 --- a/plugins/techdocs-node/src/helpers.ts +++ b/plugins/techdocs-node/src/helpers.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { Entity, @@ -143,7 +143,7 @@ export const getLocationForEntity = ( * @param opts - Options for configuring the reader, e.g. logger, etag, etc. */ export const getDocFilesFromRepository = async ( - reader: UrlReader, + reader: UrlReaderService, entity: Entity, opts?: { etag?: string; logger?: Logger }, ): Promise => { diff --git a/plugins/techdocs-node/src/stages/prepare/dir.test.ts b/plugins/techdocs-node/src/stages/prepare/dir.test.ts index a26c423aaf..df470d7da6 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.test.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.test.ts @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; import { ConfigReader } from '@backstage/config'; import { DirectoryPreparer } from './dir'; import { mockServices } from '@backstage/backend-test-utils'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; function normalizePath(path: string) { return path @@ -46,7 +47,7 @@ const createMockEntity = (annotations: {}) => { }; const mockConfig = new ConfigReader({}); -const mockUrlReader: jest.Mocked = { +const mockUrlReader: jest.Mocked = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/techdocs-node/src/stages/prepare/dir.ts b/plugins/techdocs-node/src/stages/prepare/dir.ts index 1c18b17a1e..cecd2564e6 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -38,7 +38,7 @@ import { */ export class DirectoryPreparer implements PreparerBase { private readonly scmIntegrations: ScmIntegrationRegistry; - private readonly reader: UrlReader; + private readonly reader: UrlReaderService; /** * Returns a directory preparer instance @@ -55,7 +55,7 @@ export class DirectoryPreparer implements PreparerBase { private constructor( config: Config, _logger: Logger | null, - reader: UrlReader, + reader: UrlReaderService, ) { this.reader = reader; this.scmIntegrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/techdocs-node/src/stages/prepare/types.ts b/plugins/techdocs-node/src/stages/prepare/types.ts index 79cbf68a00..14ec185c97 100644 --- a/plugins/techdocs-node/src/stages/prepare/types.ts +++ b/plugins/techdocs-node/src/stages/prepare/types.ts @@ -15,7 +15,7 @@ */ import type { Entity } from '@backstage/catalog-model'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Logger } from 'winston'; /** @@ -30,7 +30,7 @@ export type ETag = string; */ export type PreparerConfig = { logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }; /** diff --git a/plugins/techdocs-node/src/stages/prepare/url.ts b/plugins/techdocs-node/src/stages/prepare/url.ts index 84ea1ccda0..24480d2703 100644 --- a/plugins/techdocs-node/src/stages/prepare/url.ts +++ b/plugins/techdocs-node/src/stages/prepare/url.ts @@ -15,7 +15,7 @@ */ import { assertError } from '@backstage/errors'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; import { getDocFilesFromRepository } from '../../helpers'; @@ -32,7 +32,7 @@ import { */ export class UrlPreparer implements PreparerBase { private readonly logger: Logger; - private readonly reader: UrlReader; + private readonly reader: UrlReaderService; /** * Returns a directory preparer instance @@ -42,7 +42,7 @@ export class UrlPreparer implements PreparerBase { return new UrlPreparer(options.reader, options.logger); } - private constructor(reader: UrlReader, logger: Logger) { + private constructor(reader: UrlReaderService, logger: Logger) { this.logger = logger; this.reader = reader; } diff --git a/yarn.lock b/yarn.lock index fe8c64dd81..2d329b0c31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5290,6 +5290,7 @@ __metadata: "@aws-sdk/types": ^3.347.0 "@aws-sdk/util-stream-node": ^3.350.0 "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -6983,6 +6984,7 @@ __metadata: resolution: "@backstage/plugin-scaffolder-backend-module-cookiecutter@workspace:plugins/scaffolder-backend-module-cookiecutter" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -27105,6 +27107,8 @@ __metadata: resolution: "example-backend-legacy@workspace:packages/backend-legacy" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" From 2acc278202b8f73cca35753fb6d0f5c700ec239f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 14 Aug 2024 16:38:52 +0200 Subject: [PATCH 262/393] docs: update api reports Signed-off-by: Camila Belo --- packages/backend-common/api-report.md | 160 ------------------ .../api-report.md | 4 +- .../catalog-backend-module-aws/api-report.md | 4 +- .../api-report.md | 4 +- .../api-report.md | 6 +- plugins/catalog-backend/api-report.md | 12 +- .../api-report.md | 4 +- .../api-report.md | 4 +- .../api-report.md | 4 +- plugins/scaffolder-backend/api-report.md | 12 +- plugins/scaffolder-node/api-report.md | 6 +- plugins/techdocs-node/api-report.md | 6 +- 12 files changed, 33 insertions(+), 193 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 11a5befad4..61efc0f3a1 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -9,14 +9,7 @@ import { AppConfig } from '@backstage/config'; import { AuthCallback } from 'isomorphic-git'; import { AuthService } from '@backstage/backend-plugin-api'; -import { AwsCredentialsManager } from '@backstage/integration-aws-node'; -import { AwsS3Integration } from '@backstage/integration'; -import { AzureDevOpsCredentialsProvider } from '@backstage/integration'; -import { AzureIntegration } from '@backstage/integration'; import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; -import { BitbucketCloudIntegration } from '@backstage/integration'; -import { BitbucketIntegration } from '@backstage/integration'; -import { BitbucketServerIntegration } from '@backstage/integration'; import { CacheService } from '@backstage/backend-plugin-api'; import { CacheServiceOptions } from '@backstage/backend-plugin-api'; import { CacheServiceSetOptions } from '@backstage/backend-plugin-api'; @@ -28,12 +21,6 @@ import { DiscoveryService } from '@backstage/backend-plugin-api'; import Docker from 'dockerode'; import { ErrorRequestHandler } from 'express'; import express from 'express'; -import { GerritIntegration } from '@backstage/integration'; -import { GiteaIntegration } from '@backstage/integration'; -import { GithubCredentialsProvider } from '@backstage/integration'; -import { GithubIntegration } from '@backstage/integration'; -import { GitLabIntegration } from '@backstage/integration'; -import { HarnessIntegration } from '@backstage/integration'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; import { isChildPath as isChildPath_2 } from '@backstage/backend-plugin-api'; @@ -47,34 +34,18 @@ import { MergeResult } from 'isomorphic-git'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; -import { Readable } from 'stream'; import { ReadCommitResult } from 'isomorphic-git'; -import { ReadTreeOptions as ReadTreeOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponse as ReadTreeResponse_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponseDirOptions as ReadTreeResponseDirOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponseFile as ReadTreeResponseFile_2 } from '@backstage/backend-plugin-api'; -import { ReadUrlOptions as ReadUrlOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadUrlResponse as ReadUrlResponse_2 } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { SchedulerService } from '@backstage/backend-plugin-api'; -import { SearchOptions as SearchOptions_2 } from '@backstage/backend-plugin-api'; -import { SearchResponse as SearchResponse_2 } from '@backstage/backend-plugin-api'; -import { SearchResponseFile as SearchResponseFile_2 } from '@backstage/backend-plugin-api'; import { Server } from 'http'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; import { TransportStreamOptions } from 'winston-transport'; import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadTreeOptions } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadTreeResponse } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadUrlOptions } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadUrlResponse } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceSearchOptions } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceSearchResponse } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; import { V1PodTemplateSpec } from '@kubernetes/client-node'; import * as winston from 'winston'; @@ -86,31 +57,6 @@ export type AuthCallbackOptions = { logger?: LoggerService; }; -// Warning: (ae-forgotten-export) The symbol "AwsS3UrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class AwsS3UrlReader extends AwsS3UrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "AzureUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class AzureUrlReader extends AzureUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "BitbucketCloudUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class BitbucketCloudUrlReader extends BitbucketCloudUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "BitbucketServerUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class BitbucketServerUrlReader extends BitbucketServerUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "BitbucketUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class BitbucketUrlReader extends BitbucketUrlReader_2 {} - // @public @deprecated (undocumented) export type CacheClient = CacheService; @@ -245,21 +191,6 @@ export type ErrorHandlerOptions = { logClientErrors?: boolean; }; -// Warning: (ae-forgotten-export) The symbol "FetchUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class FetchUrlReader extends FetchUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "FromReadableArrayOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type FromReadableArrayOptions = FromReadableArrayOptions_2; - -// Warning: (ae-forgotten-export) The symbol "GerritUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GerritUrlReader extends GerritUrlReader_2 {} - // @public @deprecated export function getRootLogger(): winston.Logger; @@ -342,26 +273,6 @@ export class Git { resolveRef(options: { dir: string; ref: string }): Promise; } -// Warning: (ae-forgotten-export) The symbol "GiteaUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GiteaUrlReader extends GiteaUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "GithubUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GithubUrlReader extends GithubUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "GitlabUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GitlabUrlReader extends GitlabUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "HarnessUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class HarnessUrlReader extends HarnessUrlReader_2 {} - // @public @deprecated class HostDiscovery implements DiscoveryService { static fromConfig( @@ -506,50 +417,6 @@ export interface PullOptions { }; } -// Warning: (ae-forgotten-export) The symbol "ReaderFactory_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReaderFactory = ReaderFactory_2; - -// @public @deprecated (undocumented) -export type ReadTreeOptions = ReadTreeOptions_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponse = ReadTreeResponse_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponseDirOptions = ReadTreeResponseDirOptions_2; - -// Warning: (ae-forgotten-export) The symbol "ReadTreeResponseFactory_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReadTreeResponseFactory = ReadTreeResponseFactory_2; - -// Warning: (ae-forgotten-export) The symbol "ReadTreeResponseFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReadTreeResponseFactoryOptions = ReadTreeResponseFactoryOptions_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponseFile = ReadTreeResponseFile_2; - -// @public @deprecated (undocumented) -export type ReadUrlOptions = ReadUrlOptions_2; - -// @public @deprecated (undocumented) -export type ReadUrlResponse = ReadUrlResponse_2; - -// Warning: (ae-forgotten-export) The symbol "ReadUrlResponseFactory_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class ReadUrlResponseFactory extends ReadUrlResponseFactory_2 {} - -// Warning: (ae-forgotten-export) The symbol "ReadUrlResponseFactoryFromStreamOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReadUrlResponseFactoryFromStreamOptions = - ReadUrlResponseFactoryFromStreamOptions_2; - // @public @deprecated export function redactWinstonLogLine( info: winston.Logform.TransformableInfo, @@ -583,15 +450,6 @@ export type RunContainerOptions = { pullOptions?: PullOptions; }; -// @public @deprecated (undocumented) -export type SearchOptions = SearchOptions_2; - -// @public @deprecated (undocumented) -export type SearchResponse = SearchResponse_2; - -// @public @deprecated (undocumented) -export type SearchResponseFile = SearchResponseFile_2; - // @public @deprecated export class ServerTokenManager implements TokenManager { // (undocumented) @@ -667,24 +525,6 @@ export interface StatusCheckHandlerOptions { // @public @deprecated (undocumented) export type TokenManager = TokenManagerService; -// @public @deprecated (undocumented) -export type UrlReader = UrlReaderService; - -// Warning: (ae-forgotten-export) The symbol "UrlReaderPredicateTuple_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type UrlReaderPredicateTuple = UrlReaderPredicateTuple_2; - -// Warning: (ae-forgotten-export) The symbol "UrlReaders_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class UrlReaders extends UrlReaders_2 {} - -// Warning: (ae-forgotten-export) The symbol "UrlReadersOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type UrlReadersOptions = UrlReadersOptions_2; - // @public @deprecated export function useHotCleanup( _module: NodeModule, diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 97dea60170..400c55c009 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -33,7 +33,7 @@ import { ServiceRef } from '@backstage/backend-plugin-api'; import { TaskRunner } from '@backstage/backend-tasks'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { TokenManager } from '@backstage/backend-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public (undocumented) export interface BackendDynamicPlugin extends BaseDynamicPlugin { @@ -221,7 +221,7 @@ export type LegacyPluginEnvironment = { cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md index 428a39d934..cf846cf25e 100644 --- a/plugins/catalog-backend-module-aws/api-report.md +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -17,7 +17,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public export const ANNOTATION_AWS_ACCOUNT_ID: string; @@ -75,7 +75,7 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { // @public @deprecated export class AwsS3DiscoveryProcessor implements CatalogProcessor { - constructor(reader: UrlReader); + constructor(reader: UrlReaderService); // (undocumented) getProcessorName(): string; // (undocumented) diff --git a/plugins/catalog-backend-module-incremental-ingestion/api-report.md b/plugins/catalog-backend-module-incremental-ingestion/api-report.md index 4088b05bb5..92666f3928 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/api-report.md +++ b/plugins/catalog-backend-module-incremental-ingestion/api-report.md @@ -16,7 +16,7 @@ import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import type { PluginDatabaseManager } from '@backstage/backend-common'; import type { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Router } from 'express'; -import type { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public export type EntityIteratorResult = @@ -91,7 +91,7 @@ export type PluginEnvironment = { database: PluginDatabaseManager; scheduler: PluginTaskScheduler; config: Config; - reader: UrlReader; + reader: UrlReaderService; permissions: PermissionEvaluator; }; ``` diff --git a/plugins/catalog-backend-module-openapi/api-report.md b/plugins/catalog-backend-module-openapi/api-report.md index b1685c3584..05ab2e0561 100644 --- a/plugins/catalog-backend-module-openapi/api-report.md +++ b/plugins/catalog-backend-module-openapi/api-report.md @@ -12,7 +12,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-common'; import { Logger } from 'winston'; import { PlaceholderResolverParams } from '@backstage/plugin-catalog-backend'; import { ScmIntegrations } from '@backstage/integration'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public const catalogModuleJsonSchemaRefPlaceholderResolver: BackendFeatureCompat; @@ -31,14 +31,14 @@ export class OpenApiRefProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrations; logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }); // (undocumented) static fromConfig( config: Config, options: { logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }, ): OpenApiRefProcessor; // (undocumented) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f4c10d01a2..7efa8669e6 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -63,7 +63,7 @@ import { Router } from 'express'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node'; import { TokenManager } from '@backstage/backend-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Validators } from '@backstage/catalog-model'; // @public @deprecated @@ -197,7 +197,7 @@ export type CatalogEnvironment = { logger: LoggerService; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; scheduler?: PluginTaskScheduler; discovery?: DiscoveryService; @@ -254,14 +254,14 @@ export class CodeOwnersProcessor implements CatalogProcessor_2 { constructor(options: { integrations: ScmIntegrationRegistry; logger: LoggerService; - reader: UrlReader; + reader: UrlReaderService; }); // (undocumented) static fromConfig( config: Config, options: { logger: LoggerService; - reader: UrlReader; + reader: UrlReaderService; }, ): CodeOwnersProcessor; // (undocumented) @@ -413,7 +413,7 @@ export class PlaceholderProcessor implements CatalogProcessor_2 { // @public (undocumented) export type PlaceholderProcessorOptions = { resolvers: Record; - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrationRegistry; }; @@ -465,7 +465,7 @@ export function transformLegacyPolicyToProcessor( // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor_2 { - constructor(options: { reader: UrlReader; logger: LoggerService }); + constructor(options: { reader: UrlReaderService; logger: LoggerService }); // (undocumented) getProcessorName(): string; // (undocumented) diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md b/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md index b56ade73a2..df2e74337f 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md @@ -8,7 +8,7 @@ import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public const confluenceToMarkdownModule: BackendFeatureCompat; @@ -16,7 +16,7 @@ export default confluenceToMarkdownModule; // @public (undocumented) export const createConfluenceToMarkdownAction: (options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; config: Config; }) => TemplateAction< diff --git a/plugins/scaffolder-backend-module-cookiecutter/api-report.md b/plugins/scaffolder-backend-module-cookiecutter/api-report.md index 2dbf462063..a2332da624 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/api-report.md +++ b/plugins/scaffolder-backend-module-cookiecutter/api-report.md @@ -10,7 +10,7 @@ import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public const cookiecutterModule: BackendFeatureCompat; @@ -18,7 +18,7 @@ export default cookiecutterModule; // @public export function createFetchCookiecutterAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; }): TemplateAction< diff --git a/plugins/scaffolder-backend-module-rails/api-report.md b/plugins/scaffolder-backend-module-rails/api-report.md index 9b663d33d6..31e8f6834c 100644 --- a/plugins/scaffolder-backend-module-rails/api-report.md +++ b/plugins/scaffolder-backend-module-rails/api-report.md @@ -8,11 +8,11 @@ import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public export function createFetchRailsAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; allowedImageNames?: string[]; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 1e60d5b026..207b54e63f 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -63,7 +63,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; import { ZodType } from 'zod'; import { ZodTypeDef } from 'zod'; @@ -95,7 +95,7 @@ export interface CreateBuiltInActionsOptions { catalogClient: CatalogApi; config: Config; integrations: ScmIntegrations; - reader: UrlReader; + reader: UrlReaderService; } // @public @@ -154,7 +154,7 @@ export function createFetchCatalogEntityAction(options: { // @public export function createFetchPlainAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }): TemplateAction_2< { @@ -167,7 +167,7 @@ export function createFetchPlainAction(options: { // @public export function createFetchPlainFileAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }): TemplateAction_2< { @@ -180,7 +180,7 @@ export function createFetchPlainFileAction(options: { // @public export function createFetchTemplateAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; @@ -507,7 +507,7 @@ export interface RouterOptions { // (undocumented) permissions?: PermissionsService; // (undocumented) - reader: UrlReader; + reader: UrlReaderService; // (undocumented) scheduler?: PluginTaskScheduler; // (undocumented) diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 515a169cb2..8b38008eee 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -16,7 +16,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; import { Writable } from 'stream'; import { z } from 'zod'; @@ -196,7 +196,7 @@ export type ExecuteShellCommandOptions = { // @public export function fetchContents(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; @@ -206,7 +206,7 @@ export function fetchContents(options: { // @public export function fetchFile(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index f03f9e1b0e..6c7f05e900 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -15,7 +15,7 @@ import { IndexableDocument } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import * as winston from 'winston'; import { Writable } from 'stream'; @@ -82,7 +82,7 @@ export class Generators implements GeneratorBuilder { // @public export const getDocFilesFromRepository: ( - reader: UrlReader, + reader: UrlReaderService, entity: Entity, opts?: { etag?: string; @@ -157,7 +157,7 @@ export type PreparerBuilder = { // @public export type PreparerConfig = { logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }; // @public From 389f5a4cfc7e09bbad10b326529fcac57ad426e6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 14 Aug 2024 17:29:00 +0200 Subject: [PATCH 263/393] docs: add changeset files Signed-off-by: Camila Belo --- .changeset/dull-cheetahs-clap.md | 16 ++++++++++++ .changeset/fair-kangaroos-return.md | 15 +++++++++++ .changeset/healthy-carpets-reflect.md | 5 ++++ .changeset/lovely-ravens-judge.md | 36 +++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 .changeset/dull-cheetahs-clap.md create mode 100644 .changeset/fair-kangaroos-return.md create mode 100644 .changeset/healthy-carpets-reflect.md create mode 100644 .changeset/lovely-ravens-judge.md diff --git a/.changeset/dull-cheetahs-clap.md b/.changeset/dull-cheetahs-clap.md new file mode 100644 index 0000000000..a2ea657f2e --- /dev/null +++ b/.changeset/dull-cheetahs-clap.md @@ -0,0 +1,16 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-catalog-backend-module-openapi': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-techdocs-node': patch +--- + +Update deprecated url-reader-related imports. diff --git a/.changeset/fair-kangaroos-return.md b/.changeset/fair-kangaroos-return.md new file mode 100644 index 0000000000..230a6cd176 --- /dev/null +++ b/.changeset/fair-kangaroos-return.md @@ -0,0 +1,15 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Delete deprecated url reader exports: + +- ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; +- ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; +- ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` instead; +- ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` instead; +- ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` instead; +- ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; +- SearchOptions: Use `UrlReaderServiceSearchOptions` instead; +- SearchResponse: Use `UrlReaderServiceSearchResponse` instead; +- SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` instead. diff --git a/.changeset/healthy-carpets-reflect.md b/.changeset/healthy-carpets-reflect.md new file mode 100644 index 0000000000..ac7734a448 --- /dev/null +++ b/.changeset/healthy-carpets-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +Remove deprecated `urlReaderServiceFactory`, please import from `@backstage/backend-defaults/urlReader` instead. diff --git a/.changeset/lovely-ravens-judge.md b/.changeset/lovely-ravens-judge.md new file mode 100644 index 0000000000..761d8ebd1f --- /dev/null +++ b/.changeset/lovely-ravens-judge.md @@ -0,0 +1,36 @@ +--- +'@backstage/backend-common': minor +--- + +Removed the following `Url Reader` deprecated exports: + +- UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; +- AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- BitbucketUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- BitbucketCloudUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- BitbucketServerUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- GithubUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- GitlabUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- GerritUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- GiteaUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- HarnessUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- AwsS3UrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- FetchUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; +- UrlReaders: Import from `@backstage/backend-defaults/urlReader` instead; +- UrlReadersOptions: Import from `@backstage/backend-defaults/urlReader` instead; +- UrlReaderPredicateTuple: Import from `@backstage/backend-defaults/urlReader` instead; +- FromReadableArrayOptions: Import from `@backstage/backend-defaults/urlReader` instead; +- ReaderFactory: Import from `@backstage/backend-defaults/urlReader` instead; +- ReadUrlOptions:Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead; +- ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead; +- ReadUrlResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; +- ReadUrlResponseFactoryFromStreamOptions: Import from `@backstage/backend-defaults/urlReader` instead; +- ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead; +- ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead; +- ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead; +- ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead; +- ReadTreeResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; +- ReadTreeResponseFactoryOptions: Import from `@backstage/backend-defaults/urlReader` instead; +- SearchOptions: Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead; +- SearchResponse: Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead; +- SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead. From 3f4998eb9f48bfc6d21a8e9b714749e1f43fd08b Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 15 Aug 2024 09:12:39 +0200 Subject: [PATCH 264/393] chore: clarified that changes are breaking Signed-off-by: Johan Haals --- .changeset/fair-kangaroos-return.md | 2 +- .changeset/lovely-ravens-judge.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/fair-kangaroos-return.md b/.changeset/fair-kangaroos-return.md index 230a6cd176..6c5b2e57ae 100644 --- a/.changeset/fair-kangaroos-return.md +++ b/.changeset/fair-kangaroos-return.md @@ -2,7 +2,7 @@ '@backstage/backend-plugin-api': minor --- -Delete deprecated url reader exports: +**BREAKING** Deleted the following deprecated `UrlReader` exports - ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; diff --git a/.changeset/lovely-ravens-judge.md b/.changeset/lovely-ravens-judge.md index 761d8ebd1f..43611556a1 100644 --- a/.changeset/lovely-ravens-judge.md +++ b/.changeset/lovely-ravens-judge.md @@ -2,7 +2,7 @@ '@backstage/backend-common': minor --- -Removed the following `Url Reader` deprecated exports: +**BREAKING**: Removed the following `Url Reader` deprecated exports: - UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; - AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; From 6bc68ceccd7e203d8007cb4c6b1f8db653e429aa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 13:30:49 +0200 Subject: [PATCH 265/393] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- docs/frontend-system/architecture/20-extensions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index ac55a42827..e142044d03 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -20,7 +20,7 @@ Each extensions has a number of different properties that define how it behaves The ID of an extension is used to uniquely identity it, and it should ideally be unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics. -When creating an extension do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extensions) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. +When creating an extension you do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extensions) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. The extension ID will be constructed using the pattern `[:][][/][]`, where the separating `/` is only present if both a namespace and name are defined. @@ -73,7 +73,7 @@ const extension = createExtension({ }); ``` -Note that while the `createExtension` is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many [extension blueprints](./23-extension-blueprints.md) exported by both the core APIs and plugins that make it easier to create extensions for more specific usages. +Note that while the `createExtension` function is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many [extension blueprints](./23-extension-blueprints.md) exported by both the core APIs and plugins that make it easier to create extensions for more specific usages. ## Extension data @@ -104,7 +104,7 @@ const extension = createExtension({ ### Extension data uniqueness -Note that you are **not** allowed to repeat the same data reference in the outputs, or return multiple values for the same reference. Multiple outputs for the same reference will conflict with each other and cause an error. If you want to output multiple values of the same TypeScript type you should create separate references for each value. That in turn means that overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. +Note that you are **not** allowed to repeat the same data reference in the outputs, or return multiple values for the same reference. Multiple outputs for the same reference will conflict with each other and cause an error. If you want to output multiple values of the same TypeScript type you should create separate references for each value. That in turn means that overly generic extension data references are a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. ```tsx const extension = createExtension({ @@ -282,7 +282,7 @@ app: ## Extension factory as a generator function -In all examples so far we have defined the extension factory as a regular function that returns its output in an array. However, the only requirement is that the factory function returns an iterable of extension data value. This means that you can also define the factory function as a generator function, which allows you to yield values one by one. This is particularly useful if you want to conditionally output values. +In all examples so far we have defined the extension factory as a regular function that returns its output in an array. However, the only requirement is that the factory function returns any iterable of extension data values. This means that you can also define the factory function as a generator function, which allows you to yield values one by one. This is particularly useful if you want to conditionally output values. For example, this is how we could define an extension where its output depends on the configuration: From eeb5d8cb5b0ba48d9f0ec6cf276f3093da4d1709 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 13:33:57 +0200 Subject: [PATCH 266/393] docs/frontend-system: fix some extension examples Signed-off-by: Patrik Oldsberg --- .../architecture/20-extensions.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index e142044d03..02f4ba9d05 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -139,11 +139,9 @@ const extension = createExtension({ // ... output: [coreExtensionData.reactElement.optional()], factory() { - return [ - coreExtensionData.reactElement.optional()( - Math.random() < 0.5 ? : undefined, - ), - ]; + return Math.random() > 0.5 + ? [coreExtensionData.reactElement(
    Hello World
    )] + : []; }, }); ``` @@ -327,18 +325,12 @@ The `ExtensionBoundary` can be used like the following in an extension: const routableExtension = createExtension({ // ... factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); - return [ coreExtensionData.path(config.path), - coreExtensionData.routeRef(options.routeRef), + coreExtensionData.routeRef(myRouteRef), coreExtensionData.reactElement( - + , ), ]; From d897ce63d619685fe1f9aec27a809f6f132abbba Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 15 Aug 2024 13:43:32 +0200 Subject: [PATCH 267/393] Remove backend-common dependency Signed-off-by: Johan Haals --- plugins/search-backend-node/api-report.md | 4 ++-- plugins/search-backend-node/package.json | 2 +- ...ewlineDelimitedJsonCollatorFactory.test.ts | 15 ++++++------ .../NewlineDelimitedJsonCollatorFactory.ts | 7 +++--- plugins/techdocs-node/src/helpers.test.ts | 24 +++++++++---------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index 5e8961a65e..03269c72e8 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -20,7 +20,7 @@ import { SearchQuery } from '@backstage/plugin-search-common'; import { TaskFunction } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; import { Transform } from 'stream'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Writable } from 'stream'; // @public @@ -138,7 +138,7 @@ export class NewlineDelimitedJsonCollatorFactory export type NewlineDelimitedJsonCollatorFactoryOptions = { type: string; searchPattern: string; - reader: UrlReader; + reader: UrlReaderService; logger: LoggerService; visibilityPermission?: Permission; }; diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index d7a7191faa..a97a0a0a7c 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -53,7 +53,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts index 45cc98e9ba..ce640636f9 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts @@ -13,17 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { - ReadUrlResponse, - UrlReader, - UrlReaders, -} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Readable } from 'stream'; import { NewlineDelimitedJsonCollatorFactory } from './NewlineDelimitedJsonCollatorFactory'; import { TestPipeline } from '../test-utils'; import { mockServices } from '@backstage/backend-test-utils'; +import { + UrlReaderService, + UrlReaderServiceReadUrlResponse, +} from '@backstage/backend-plugin-api'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; describe('DefaultCatalogCollatorFactory', () => { const config = new ConfigReader({}); @@ -42,7 +41,9 @@ describe('DefaultCatalogCollatorFactory', () => { describe('getCollator', () => { let readable: Readable; let reader: jest.Mocked< - UrlReader & { readUrl: jest.Mock> } + UrlReaderService & { + readUrl: jest.Mock>; + } >; let factory: NewlineDelimitedJsonCollatorFactory; diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index e7d749dd41..bf78b98040 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -18,9 +18,8 @@ import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Permission } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; -import { UrlReader } from '@backstage/backend-common'; import { parse as parseNdjson } from 'ndjson'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; /** * Options for instantiate NewlineDelimitedJsonCollatorFactory @@ -29,7 +28,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; export type NewlineDelimitedJsonCollatorFactoryOptions = { type: string; searchPattern: string; - reader: UrlReader; + reader: UrlReaderService; logger: LoggerService; visibilityPermission?: Permission; }; @@ -73,7 +72,7 @@ export class NewlineDelimitedJsonCollatorFactory private constructor( type: string, private readonly searchPattern: string, - private readonly reader: UrlReader, + private readonly reader: UrlReaderService, private readonly logger: LoggerService, visibilityPermission: Permission | undefined, ) { diff --git a/plugins/techdocs-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index 4f77a5432b..33f4018f47 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -14,13 +14,6 @@ * limitations under the License. */ -import { - ReadTreeResponse, - ReadUrlOptions, - ReadUrlResponse, - SearchResponse, - UrlReader, -} from '@backstage/backend-common'; import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -34,6 +27,13 @@ import { parseReferenceAnnotation, transformDirLocation, } from './helpers'; +import { + UrlReaderService, + UrlReaderServiceSearchResponse, + UrlReaderServiceReadUrlResponse, + UrlReaderServiceReadUrlOptions, + UrlReaderServiceReadTreeResponse, +} from '@backstage/backend-plugin-api'; jest.mock('@backstage/catalog-model', () => ({ ...jest.requireActual('@backstage/catalog-model'), @@ -290,19 +290,19 @@ describe('getLocationForEntity', () => { describe('getDocFilesFromRepository', () => { it('should read a remote directory using UrlReader.readTree', async () => { - class MockUrlReader implements UrlReader { + class MockUrlReader implements UrlReaderService { async read() { return Buffer.from('mock'); } async readUrl( _url: string, - _options?: ReadUrlOptions | undefined, - ): Promise { + _options?: UrlReaderServiceReadUrlOptions | undefined, + ): Promise { throw new Error('Method not implemented.'); } - async readTree(): Promise { + async readTree(): Promise { return { dir: async () => { return '/tmp/testfolder'; @@ -317,7 +317,7 @@ describe('getDocFilesFromRepository', () => { }; } - async search(): Promise { + async search(): Promise { return { etag: '', files: [], From 12a6126adb1383ee760fd53f4cc27e215dabe1c8 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 15 Aug 2024 13:45:51 +0200 Subject: [PATCH 268/393] chore: fix yarn lock Signed-off-by: Johan Haals --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index 2d329b0c31..0c63617ae7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7559,6 +7559,7 @@ __metadata: resolution: "@backstage/plugin-search-backend-node@workspace:plugins/search-backend-node" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From f149bfc4f8610c07f079fc31cf681030c4a2647d Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 13:29:46 +0200 Subject: [PATCH 269/393] chore: remove the needless thing Signed-off-by: blam --- .../src/blueprints/SignInPageBlueprint.test.tsx | 6 ++---- .../src/app/createExtensionTester.test.tsx | 7 +++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx index b45d8ead15..e6389da574 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx @@ -62,11 +62,9 @@ describe('SignInPageBlueprint', () => { const tester = createExtensionTester(extension); - const Element = tester.get(SignInPageBlueprint.dataRefs.component); + const Component = tester.get(SignInPageBlueprint.dataRefs.component); - expect(Element).toBeDefined(); - - renderInTestApp( {}} />); + renderInTestApp( {}} />); await waitFor(() => { expect(screen.getByTestId('mock-sign-in')).toBeInTheDocument(); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 13bdca1990..73220419b7 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -339,6 +339,9 @@ describe('createExtensionTester', () => { const extension = createExtension({ namespace: 'test', name: 'e1', + inputs: { + ignored: createExtensionInput([stringDataRef]), + }, attachTo: { id: 'ignored', input: 'ignored' }, output: [coreExtensionData.reactElement], factory: () => [coreExtensionData.reactElement(
    bob
    )], @@ -346,8 +349,8 @@ describe('createExtensionTester', () => { const extraExtension = createExtension({ namespace: 'test', - name: 'e1', - attachTo: { id: 'ignored', input: 'ignored' }, + name: 'e2', + attachTo: { id: 'test/e1', input: 'ignored' }, output: [stringDataRef, internalRef.optional()], factory: () => [stringDataRef('test-text')], }); From f8e16b39aeee68fa449ce8d9bac3e9f5747281ea Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 13:50:03 +0200 Subject: [PATCH 270/393] docs/frontend-system: add missing plugin doc fixes Signed-off-by: Patrik Oldsberg --- docs/frontend-system/architecture/15-plugins.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index f1a24b5b4d..a54573427d 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -10,19 +10,20 @@ description: Frontend plugins ## Introduction -Frontend plugins are a foundational building block in Backstage and the frontend system. They are used to encapsulate and provide functionality for a Backstage app, such as new pages, navigational elements, APIs, as well as extensions and features for other plugins, such entity page cards and content for the Software Catalog, or result list items for the search plugin. +Frontend plugins are a foundational building block in Backstage and the frontend system. They are used to encapsulate and provide functionality for a Backstage app, such as new pages, navigational elements, and APIs; as well as extensions and features for other plugins, such as entity page cards and content for the Software Catalog, or result list items for the search plugin. -Each plugin is typically shipped in a separate NPM package, whether that's a published package, or just in the local workspace. The plugins instance should always the `default` export of the package, either via the main entry-point or the `/alpha` sub-path export. Each plugin package is limited to exporting a single plugin instance. In a local workspace you could use a different structure if preferred, but this is considered a non-standard layout and should be avoided in published packages. +Each plugin is typically shipped in a separate NPM package, whether that's a published package, or just in the local workspace. The plugin instance should always the `default` export of the package, either via the main entry-point or the `/alpha` sub-path export. Each plugin package is limited to exporting a single plugin instance. In a local workspace you could use a different structure if preferred, but this is considered a non-standard layout and should be avoided in published packages. ## Creating a Plugin Frontend plugin instances are created with the `createFrontendPlugin` function, which is provided by the `@backstage/frontend-plugin-api` package. It takes a single options object that provides all of the necessary configuration for the plugin. In particular you will want to provide [extensions](./20-extensions.md) for your plugin, as that is the way that you can provide new functionality to the app. -```ts +```tsx // This creates a new extension, see "Extension Blueprints" documentation for more details const myPage = PageBlueprint.make({ params: { defaultPath: '/my-page', + loader: () => import('./MyPage').then(m => ), }, }); @@ -34,7 +35,7 @@ export default createFrontendPlugin({ ### `pluginId` option -Each plugin needs an ID, which is used to uniquely identity the plugin within an entity Backstage system. The ID does not have to be globally unique across all of the NPM ecosystem, although you generally want to strive for that. It is not possible to install multiple plugins with the same ID in a single Backstage app. +Each plugin needs an ID, which is used to uniquely identify the plugin within an entire Backstage system. The ID does not have to be globally unique across all of the NPM ecosystem, although you generally want to strive for that. It is not possible to install multiple plugins with the same ID in a single Backstage app. The plugin ID should generally be part of the of the package name and use kebab-case. See both the [frontend naming patterns section](./50-naming-patterns.md), as well as the [package metadata section](../../tooling/package-metadata.md#name) for more information. @@ -42,11 +43,11 @@ The plugin ID should generally be part of the of the package name and use kebab- These are the [extensions](./20-extensions.md) that the plugin provides to the app. Note that you should not export any of these extensions separately from the plugin package, as they can already by accessed via the `getExtension` method of the plugin instance using the extension ID. -The extensions that you provide to a plugin will have their `namespace` set to the plugin ID by default. For example, if you create an extensions using the `PageBlueprint` without any particular naming options and install that via a plugin with the ID `my-plugin`, the final extension ID will be `page:my-plugin`. You can read more about how this works in the [extension structure documentation](./20-extensions.md#extension-structure). +The extensions that you provide to a plugin will have their `namespace` set to the plugin ID by default. For example, if you create an extension using the `PageBlueprint` without any particular naming options and install that via a plugin with the ID `my-plugin`, the final extension ID will be `page:my-plugin`. You can read more about how this works in the [extension structure documentation](./20-extensions.md#extension-structure). ### `routes` and `externalRoutes` options -These are the routes that the plugin exposes to the app. The `routes` option declares all of the target routes that your plugin provides, i.e. routes that other plugins and link to. The `externalRoutes` option instead declares all the outgoing routes, i.e. routes that your plugins links to, which you can bind to the `routes` of other plugins. See the [routes documentation](./36-routes.md) for more information how to set up cross-plugin navigation. +These are the routes that the plugin exposes to the app. The `routes` option declares all of the target routes that your plugin provides, i.e. routes that other plugins link to. The `externalRoutes` option instead declares all the outgoing routes, i.e. routes that your plugins links to, which you can bind to the `routes` of other plugins. See the [routes documentation](./36-routes.md) for more information how to set up cross-plugin navigation. ### `featureFlags` option @@ -54,7 +55,7 @@ This is a list of feature flag declarations that your plugin provides to the app ## Installing a Plugin in an App -A plugin instance is considered an frontend feature and can be installed directly in any Backstage frontend app. See the [app documentation](./10-app.md) for more information about the different ways in which you can install new features in an app. +A plugin instance is considered a frontend feature and can be installed directly in any Backstage frontend app. See the [app documentation](./10-app.md) for more information about the different ways in which you can install new features in an app. ## Overriding a Plugin @@ -87,4 +88,4 @@ export default plugin.withOverrides({ }); ``` -You can keep the plugin override in your app package, but it can often be a good idea to separate it out into its own package, especially if you the overrides are complex or you want distinct ownership of the override. For example, if you are overriding the `@backstage/plugin-catalog` plugin, you might create a new package called `@internal/plugin-catalog` at `plugins/catalog` in your workspace, which exports the overridden plugin instance. +You can keep the plugin override in your app package, but it can often be a good idea to separate it out into its own package, especially if the overrides are complex or you want distinct ownership of the override. For example, if you are overriding the `@backstage/plugin-catalog` plugin, you might create a new package called `@internal/plugin-catalog` at `plugins/catalog` in your workspace, which exports the overridden plugin instance. From 805934e7342d0fb069206a7bf0eb55879f22122d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 15 Aug 2024 13:55:29 +0200 Subject: [PATCH 271/393] Update plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts Signed-off-by: Camila Belo --- .../src/scaffolder/actions/builtin/fetch/template.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 87a6c84750..61918e8450 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -80,7 +80,7 @@ describe('fetch:template', () => { workspace: {}, }); action = createFetchTemplateAction({ - reader: Symbol('UrlReaderService') as unknown as UrlReaderService, + reader: Symbol('UrlReader') as unknown as UrlReaderService, integrations: Symbol('Integrations') as unknown as ScmIntegrations, }); }); From b324f97150e02612f6273ad5cecdadce1d6cf813 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 14:34:17 +0200 Subject: [PATCH 272/393] docs/frontend-system: initial extension override docs Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 42a7f365a1..c3fce5098d 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -10,9 +10,25 @@ description: Frontend extension overrides ## Introduction -An extension override is a building block of the frontend system that allows you to programmatically override app or plugin extensions anywhere in your application. Since the entire application is built mostly out of extensions from the bottom up, this is a powerful feature. You can use it for example to provide your own app root layout, to replace the implementation of a Utility API with a custom one, to override how the catalog page renders itself, and much more. +An important customization point in the frontend system is the ability to override existing extensions. It can be used for anything from slight tweaks to the extension logic, to completely replacing an extension with a custom implementation. While extensions are encouraged to make themselves configurable, there are many situations where you need to override an extension to achieve the desired behavior. The ability to override extensions should be kept in mind when building plugins, and can be a powerful tool to allow for deeper customizations without the need to re-implement large parts of the plugin. -In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible, and only use extension overrides when it's necessary to entirely replace the extension. Check the respective extension documentation for guidance. +In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides. + +## Overriding an extension + +Every extension created with `createExtension` comes with an `override` method, including those created from an [extension blueprint](./23-extension-blueprints.md). The `override` method **creates a new extension**, it does not mutate the existing extensions. This new extension in created in such a way that if it is installed adjacent to the existing extension, it will take precedence and override the existing extension. While the `override` method does create new extension instances, it is not intended to be used as a way to create multiple new extensions from a base template, for that use-case you will want to use an [extension blueprint](./23-extension-blueprints.md) instead. + +The following is an example of calling the `.override(...)` method on an extension: + +```tsx +const myOverrideExtension = myExtension.override({ + factory(originalFactory) { + return originalFactory(); + }, +}); +``` + +This override is a no-op, it does not change the behavior of the extension, but simply forwards the outputs from the original extension factory. If you are familiar with [extension blueprints](./23-extension-blueprints.md), you will recognize this factory override pattern where we get access to the original factory function. In fact the only difference is that we do not need to pass any parameters to the original factory. The first parameter is now instead the optional factory context overrides, more on that as we dive into each override pattern in the following sections. ## Override App Extensions From 8c21f6ed0b30b640f6a708266622846bfefbd59b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 14:35:54 +0200 Subject: [PATCH 273/393] docs/frontend-system: document how to override factory outputs Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index c3fce5098d..f988082258 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -30,6 +30,55 @@ const myOverrideExtension = myExtension.override({ This override is a no-op, it does not change the behavior of the extension, but simply forwards the outputs from the original extension factory. If you are familiar with [extension blueprints](./23-extension-blueprints.md), you will recognize this factory override pattern where we get access to the original factory function. In fact the only difference is that we do not need to pass any parameters to the original factory. The first parameter is now instead the optional factory context overrides, more on that as we dive into each override pattern in the following sections. +## Overriding original factory outputs + +When overriding an extension you can choose to forward the existing outputs, or replace them with your own. The override factory has an exception to the rule that extension factories can only return a single value for each declared output. It will instead always use the **last** value provided for each extension data reference. This makes it possible to forward the outputs from the original factory, but also provide your own, for example: + +```tsx +const myOverrideExtension = myExtension.override({ + factory(originalFactory) { + return [ + ...originalFactory(), + coreExtensionData.reactElement(

    Hello Override

    ), + ]; + }, +}); +``` + +You can also access individual data values from the original factory, in order to decorate the output: + +```tsx +const myOverrideExtension = myExtension.override({ + factory(originalFactory) { + const originalOutput = originalFactory(); + const originalElement = originalOutput.get(coreExtensionData.reactElement); + + return [ + ...originalOutput, + coreExtensionData.reactElement( +
    + Show original element + {originalElement} +
    , + ), + ]; + }, +}); +``` + +Just as [extension factories can be declared as a generator function](./20-extensions.md#extension-factory-as-a-generator-function), so can the override factory. Using a generator function, the first example above can be written as follows: + +```tsx +const myOverrideExtension = myExtension.override({ + *factory(originalFactory) { + yield* originalFactory(); + yield coreExtensionData.reactElement(

    Hello Override

    ); + }, +}); +``` + +Note the `yield*` expression, which forwards all values from the provided iterable to the generator, in this case the original factory output. + ## Override App Extensions In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. From cc2ee1d05fe9d450a6fa5fa960c89c972657e77d Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 09:00:09 +0200 Subject: [PATCH 274/393] chore: fix deprecations in app-next Signed-off-by: blam --- packages/app-next/src/App.tsx | 35 +++-- .../app-next/src/examples/pagesPlugin.tsx | 142 +++++++++--------- .../app-next/src/overrides/SignInPage.tsx | 9 +- 3 files changed, 101 insertions(+), 85 deletions(-) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index b57c680474..c41f33e01b 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -26,8 +26,8 @@ import homePlugin, { import { coreExtensionData, createExtension, - createApiExtension, createExtensionOverrides, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; import techdocsPlugin from '@backstage/plugin-techdocs/alpha'; import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; @@ -40,6 +40,7 @@ import { createApiFactory, configApiRef } from '@backstage/core-plugin-api'; import { ScmAuth, ScmIntegrationsApi, + scmAuthApiRef, scmIntegrationsApiRef, } from '@backstage/integration-react'; import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; @@ -77,25 +78,31 @@ TODO: const homePageExtension = createExtension({ name: 'myhomepage', attachTo: { id: 'page:home', input: 'props' }, - output: { - children: coreExtensionData.reactElement, - title: titleExtensionDataRef, - }, + output: [coreExtensionData.reactElement, titleExtensionDataRef], factory() { - return { children: homePage, title: 'just a title' }; + return [ + coreExtensionData.reactElement(homePage), + titleExtensionDataRef('just a title'), + ]; }, }); -const scmAuthExtension = createApiExtension({ - factory: ScmAuth.createDefaultApiFactory(), +const scmAuthExtension = ApiBlueprint.make({ + namespace: scmAuthApiRef.id, + params: { + factory: ScmAuth.createDefaultApiFactory(), + }, }); -const scmIntegrationApi = createApiExtension({ - factory: createApiFactory({ - api: scmIntegrationsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), - }), +const scmIntegrationApi = ApiBlueprint.make({ + namespace: scmIntegrationsApiRef.id, + params: { + factory: createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + }, }); const collectedLegacyPlugins = convertLegacyApp( diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index 2b9907dd59..8eca7d1e6c 100644 --- a/packages/app-next/src/examples/pagesPlugin.tsx +++ b/packages/app-next/src/examples/pagesPlugin.tsx @@ -17,11 +17,11 @@ import React from 'react'; import { Link } from '@backstage/core-components'; import { - createPageExtension, createFrontendPlugin, createRouteRef, createExternalRouteRef, useRouteRef, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import { Route, Routes } from 'react-router-dom'; @@ -37,91 +37,97 @@ export const pageXRouteRef = createRouteRef(); // path: '/page2', // }); -const IndexPage = createPageExtension({ +const IndexPage = PageBlueprint.make({ name: 'index', - defaultPath: '/', - routeRef: indexRouteRef, - loader: async () => { - const Component = () => { - const page1Link = useRouteRef(page1RouteRef); - return ( -
    - op - {page1Link && ( + params: { + defaultPath: '/', + routeRef: indexRouteRef, + loader: async () => { + const Component = () => { + const page1Link = useRouteRef(page1RouteRef); + return ( +
    + op + {page1Link && ( +
    + Page 1 +
    + )}
    - Page 1 + Home +
    +
    + GraphiQL +
    +
    + Search +
    +
    + Settings
    - )} -
    - Home
    -
    - GraphiQL -
    -
    - Search -
    -
    - Settings -
    -
    - ); - }; - return ; + ); + }; + return ; + }, }, }); -const Page1 = createPageExtension({ +const Page1 = PageBlueprint.make({ name: 'page1', - defaultPath: '/page1', - routeRef: page1RouteRef, - loader: async () => { - const Component = () => { - const indexLink = useRouteRef(indexRouteRef); - const xLink = useRouteRef(externalPageXRouteRef); - // const page2Link = useRouteRef(page2RouteRef); - - return ( -
    -

    This is page 1

    - {indexLink && Go back} - Page 2 - {/* Page 2 */} - {xLink && Page X} + params: { + defaultPath: '/page1', + routeRef: page1RouteRef, + loader: async () => { + const Component = () => { + const indexLink = useRouteRef(indexRouteRef); + const xLink = useRouteRef(externalPageXRouteRef); + // const page2Link = useRouteRef(page2RouteRef); + return (
    - Sub-page content: +

    This is page 1

    + {indexLink && Go back} + Page 2 + {/* Page 2 */} + {xLink && Page X} +
    - - This is also page 1} /> - This is page 2} /> - + Sub-page content: +
    + + This is also page 1} /> + This is page 2} /> + +
    -
    - ); - }; - return ; + ); + }; + return ; + }, }, }); -const ExternalPage = createPageExtension({ +const ExternalPage = PageBlueprint.make({ name: 'pageX', - defaultPath: '/pageX', - routeRef: pageXRouteRef, - loader: async () => { - const Component = () => { - const indexLink = useRouteRef(indexRouteRef); - // const pageXLink = useRouteRef(pageXRouteRef); + params: { + defaultPath: '/pageX', + routeRef: pageXRouteRef, + loader: async () => { + const Component = () => { + const indexLink = useRouteRef(indexRouteRef); + // const pageXLink = useRouteRef(pageXRouteRef); - return ( -
    -

    This is page X

    - {indexLink && Go back} -
    - ); - }; - return ; + return ( +
    +

    This is page X

    + {indexLink && Go back} +
    + ); + }; + return ; + }, }, }); diff --git a/packages/app-next/src/overrides/SignInPage.tsx b/packages/app-next/src/overrides/SignInPage.tsx index 364a749293..52dd01fcde 100644 --- a/packages/app-next/src/overrides/SignInPage.tsx +++ b/packages/app-next/src/overrides/SignInPage.tsx @@ -17,13 +17,16 @@ import React from 'react'; import { SignInPage } from '@backstage/core-components'; import { + SignInPageBlueprint, createExtensionOverrides, - createSignInPageExtension, } from '@backstage/frontend-plugin-api'; -const signInPage = createSignInPageExtension({ +const signInPage = SignInPageBlueprint.make({ name: 'guest', - loader: async () => props => , + params: { + loader: async () => props => + , + }, }); export const signInPageOverrides = createExtensionOverrides({ From 95f977f42cefdf8e188c33fca2364af4329a933f Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 09:02:38 +0200 Subject: [PATCH 275/393] migrate core-compat-api Signed-off-by: blam --- .../src/collectLegacyRoutes.test.tsx | 12 +-- .../src/collectLegacyRoutes.tsx | 74 +++++++++++-------- .../src/compatWrapper/compatWrapper.test.tsx | 8 +- .../src/convertLegacyApp.test.tsx | 8 +- .../core-compat-api/src/convertLegacyApp.ts | 31 ++++---- 5 files changed, 69 insertions(+), 64 deletions(-) diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index b721592ffd..f8b40a561c 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -70,7 +70,7 @@ describe('collectLegacyRoutes', () => { id: 'page:score-card', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'score-board' }, + defaultConfig: {}, }, { id: 'api:plugin.scoringdata.service', @@ -86,7 +86,7 @@ describe('collectLegacyRoutes', () => { id: 'page:stackstorm', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'stackstorm' }, + defaultConfig: {}, }, { id: 'api:plugin.stackstorm.service', @@ -102,13 +102,13 @@ describe('collectLegacyRoutes', () => { id: 'page:puppetDb', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'page:puppetDb/1', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'api:plugin.puppetdb.service', @@ -173,12 +173,12 @@ describe('collectLegacyRoutes', () => { id: 'page:catalog', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'catalog' }, + defaultConfig: {}, }, { id: 'page:catalog/1', attachTo: { id: 'app/routes', input: 'routes' }, - defaultConfig: { path: 'catalog/:namespace/:kind/:name' }, + defaultConfig: {}, disabled: false, }, { diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 9af994d4e0..68e9aaf3a1 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -29,6 +29,8 @@ import { createExtensionInput, createPageExtension, createFrontendPlugin, + ApiBlueprint, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import React, { Children, ReactNode, isValidElement } from 'react'; import { Route, Routes } from 'react-router-dom'; @@ -80,19 +82,24 @@ function makeRoutingShimExtension(options: { name, attachTo: { id: parentExtensionId, input: 'childRoutingShims' }, inputs: { - childRoutingShims: createExtensionInput({ - routePath: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), - }), + childRoutingShims: createExtensionInput([ + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ]), }, - output: { - routePath: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), + output: [ + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ], + *factory() { + if (routePath) { + yield coreExtensionData.routePath(routePath); + } + + if (routeRef) { + yield coreExtensionData.routeRef(convertLegacyRouteRef(routeRef)); + } }, - factory: () => ({ - routePath, - routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, - }), }); } @@ -214,28 +221,33 @@ export function collectLegacyRoutes( }`; extensions.push( - createPageExtension({ + PageBlueprint.makeWithOverrides({ name: pageExtensionName, - defaultPath: path[0] === '/' ? path.slice(1) : path, - routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, inputs: { - childRoutingShims: createExtensionInput({ - routePath: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), - }), + childRoutingShims: createExtensionInput([ + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ]), + }, + factory(originalFactory, { inputs: _inputs }) { + // todo(blam): why do we not use the inputs here? + return originalFactory({ + defaultPath: path[0] === '/' ? path.slice(1) : path, + routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, + loader: async () => + compatWrapper( + route.props.children ? ( + + + + + + ) : ( + routeElement + ), + ), + }); }, - loader: async () => - compatWrapper( - route.props.children ? ( - - - - - - ) : ( - routeElement - ), - ), }), ); @@ -258,7 +270,7 @@ export function collectLegacyRoutes( extensions: [ ...extensions, ...Array.from(plugin.getApis()).map(factory => - createApiExtension({ factory }), + ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), ), ], routes: convertLegacyRouteRefs(plugin.routes ?? {}), diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx index 04e2361087..6357f13906 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -45,9 +45,7 @@ describe('BackwardsCompatProvider', () => { createExtensionTester( createExtension({ attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], factory() { function Component() { const app = useApp(); @@ -66,9 +64,7 @@ describe('BackwardsCompatProvider', () => { ); } - return { - element: compatWrapper(), - }; + return [coreExtensionData.reactElement(compatWrapper())]; }, }), ).render(); diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 2e7be66915..ea84c06ba9 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -62,7 +62,7 @@ describe('convertLegacyApp', () => { id: 'page:score-card', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'score-board' }, + defaultConfig: {}, }, { id: 'api:plugin.scoringdata.service', @@ -78,7 +78,7 @@ describe('convertLegacyApp', () => { id: 'page:stackstorm', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'stackstorm' }, + defaultConfig: {}, }, { id: 'api:plugin.stackstorm.service', @@ -94,13 +94,13 @@ describe('convertLegacyApp', () => { id: 'page:puppetDb', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'page:puppetDb/1', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'api:plugin.puppetdb.service', diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 72ce4b5e61..ffeab3bdc6 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -107,33 +107,30 @@ export function convertLegacyApp( name: 'layout', attachTo: { id: 'app', input: 'root' }, inputs: { - content: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, - ), - }, - output: { - element: coreExtensionData.reactElement, + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { // Clone the root element, this replaces the FlatRoutes declared in the app with out content input - return { - element: React.cloneElement( - rootEl, - undefined, - inputs.content.output.element, + return [ + coreExtensionData.reactElement( + React.cloneElement( + rootEl, + undefined, + inputs.content.get(coreExtensionData.reactElement), + ), ), - }; + ]; }, }); const CoreNavOverride = createExtension({ namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], disabled: true, }); From 7df9a72154944de6aaf9ecd129af565a78409ad5 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 09:37:13 +0200 Subject: [PATCH 276/393] chore: updated frontend-app-api Signed-off-by: blam --- .../DefaultComponentsApi.test.tsx | 10 +-- .../frontend-app-api/src/extensions/App.tsx | 57 ++++++--------- .../src/extensions/AppLayout.tsx | 40 ++++------- .../src/extensions/AppNav.tsx | 48 ++++++------- .../src/extensions/AppRoot.tsx | 64 +++++++++-------- .../src/extensions/AppRoutes.tsx | 22 +++--- .../src/extensions/elements.tsx | 49 +++++++------ .../src/extensions/themes.tsx | 44 +++++++----- .../extractRouteInfoFromAppNode.test.ts | 29 ++++---- .../src/tree/createAppTree.test.ts | 8 +-- .../src/tree/resolveAppTree.test.ts | 4 +- .../src/wiring/createApp.test.tsx | 71 +++++++++++-------- .../frontend-app-api/src/wiring/createApp.tsx | 20 +++--- 13 files changed, 229 insertions(+), 237 deletions(-) diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx index 7d62103727..e2c7581e63 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx @@ -40,14 +40,8 @@ const baseOverrides = createExtensionOverrides({ namespace: 'app', name: 'root', attachTo: { id: 'app', input: 'root' }, - output: { - element: coreExtensionData.reactElement, - }, - factory() { - return { - element:
    root
    , - }; - }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
    root
    )], }), ], }); diff --git a/packages/frontend-app-api/src/extensions/App.tsx b/packages/frontend-app-api/src/extensions/App.tsx index 7794aceb79..ba6c5f65f1 100644 --- a/packages/frontend-app-api/src/extensions/App.tsx +++ b/packages/frontend-app-api/src/extensions/App.tsx @@ -18,51 +18,38 @@ import React from 'react'; import { ExtensionBoundary, coreExtensionData, - createApiExtension, createComponentExtension, createExtension, createExtensionInput, - createThemeExtension, - createTranslationExtension, IconBundleBlueprint, + ThemeBlueprint, + ApiBlueprint, + TranslationBlueprint, } from '@backstage/frontend-plugin-api'; export const App = createExtension({ namespace: 'app', attachTo: { id: 'root', input: 'default' }, // ignored inputs: { - apis: createExtensionInput({ - api: createApiExtension.factoryDataRef, + apis: createExtensionInput([ApiBlueprint.dataRefs.factory]), + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme]), + components: createExtensionInput([ + createComponentExtension.componentDataRef, + ]), + translations: createExtensionInput([ + TranslationBlueprint.dataRefs.translation, + ]), + icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons]), + root: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, }), - themes: createExtensionInput({ - theme: createThemeExtension.themeDataRef, - }), - components: createExtensionInput({ - component: createComponentExtension.componentDataRef, - }), - translations: createExtensionInput({ - translation: createTranslationExtension.translationDataRef, - }), - icons: createExtensionInput({ - icon: IconBundleBlueprint.dataRefs.icons, - }), - root: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, + }, + output: [coreExtensionData.reactElement], + factory: ({ node, inputs }) => [ + coreExtensionData.reactElement( + + {inputs.root.get(coreExtensionData.reactElement)} + , ), - }, - output: { - root: coreExtensionData.reactElement, - }, - factory({ node, inputs }) { - return { - root: ( - - {inputs.root.output.element} - - ), - }; - }, + ], }); diff --git a/packages/frontend-app-api/src/extensions/AppLayout.tsx b/packages/frontend-app-api/src/extensions/AppLayout.tsx index 7be0029c25..9fadba8a93 100644 --- a/packages/frontend-app-api/src/extensions/AppLayout.tsx +++ b/packages/frontend-app-api/src/extensions/AppLayout.tsx @@ -27,30 +27,20 @@ export const AppLayout = createExtension({ name: 'layout', attachTo: { id: 'app/root', input: 'children' }, inputs: { - nav: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, + nav: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + }, + output: [coreExtensionData.reactElement], + factory: ({ inputs }) => [ + coreExtensionData.reactElement( + + {inputs.nav.get(coreExtensionData.reactElement)} + {inputs.content.get(coreExtensionData.reactElement)} + , ), - content: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, - ), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - {inputs.nav.output.element} - {inputs.content.output.element} - - ), - }; - }, + ], }); diff --git a/packages/frontend-app-api/src/extensions/AppNav.tsx b/packages/frontend-app-api/src/extensions/AppNav.tsx index ffef1c7c61..49c95e84f2 100644 --- a/packages/frontend-app-api/src/extensions/AppNav.tsx +++ b/packages/frontend-app-api/src/extensions/AppNav.tsx @@ -86,33 +86,27 @@ export const AppNav = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, + items: createExtensionInput([createNavItemExtension.targetDataRef]), + logos: createExtensionInput([createNavLogoExtension.logoElementsDataRef], { + singleton: true, + optional: true, }), - logos: createExtensionInput( - { - elements: createNavLogoExtension.logoElementsDataRef, - }, - { - singleton: true, - optional: true, - }, + }, + output: [coreExtensionData.reactElement], + factory: ({ inputs }) => [ + coreExtensionData.reactElement( + + + + {inputs.items.map((item, index) => ( + + ))} + , ), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - - - {inputs.items.map((item, index) => ( - - ))} - - ), - }; - }, + ], }); diff --git a/packages/frontend-app-api/src/extensions/AppRoot.tsx b/packages/frontend-app-api/src/extensions/AppRoot.tsx index f3a962dd10..a158386d04 100644 --- a/packages/frontend-app-api/src/extensions/AppRoot.tsx +++ b/packages/frontend-app-api/src/extensions/AppRoot.tsx @@ -23,12 +23,12 @@ import React, { useState, } from 'react'; import { + AppRootWrapperBlueprint, + RouterBlueprint, + SignInPageBlueprint, coreExtensionData, - createAppRootWrapperExtension, createExtension, createExtensionInput, - createRouterExtension, - createSignInPageExtension, } from '@backstage/frontend-plugin-api'; import { IdentityApi, @@ -48,52 +48,54 @@ export const AppRoot = createExtension({ name: 'root', attachTo: { id: 'app', input: 'root' }, inputs: { - router: createExtensionInput( - { component: createRouterExtension.componentDataRef }, - { singleton: true, optional: true }, - ), - signInPage: createExtensionInput( - { component: createSignInPageExtension.componentDataRef }, - { singleton: true, optional: true }, - ), - children: createExtensionInput( - { element: coreExtensionData.reactElement }, - { singleton: true }, - ), - elements: createExtensionInput({ - element: coreExtensionData.reactElement, + router: createExtensionInput([RouterBlueprint.dataRefs.component], { + singleton: true, + optional: true, }), - wrappers: createExtensionInput({ - component: createAppRootWrapperExtension.componentDataRef, + signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], { + singleton: true, + optional: true, }), + children: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + elements: createExtensionInput([coreExtensionData.reactElement]), + wrappers: createExtensionInput([ + AppRootWrapperBlueprint.dataRefs.component, + ]), }, - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], factory({ inputs }) { let content: React.ReactNode = ( <> {inputs.elements.map(el => ( - {el.output.element} + + {el.get(coreExtensionData.reactElement)} + ))} - {inputs.children.output.element} + {inputs.children.get(coreExtensionData.reactElement)} ); for (const wrapper of inputs.wrappers) { - content = {content}; + const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component); + content = {content}; } - return { - element: ( + return [ + coreExtensionData.reactElement( {content} - + , ), - }; + ]; }, }); diff --git a/packages/frontend-app-api/src/extensions/AppRoutes.tsx b/packages/frontend-app-api/src/extensions/AppRoutes.tsx index 38a9291755..6ef7e2a271 100644 --- a/packages/frontend-app-api/src/extensions/AppRoutes.tsx +++ b/packages/frontend-app-api/src/extensions/AppRoutes.tsx @@ -29,15 +29,13 @@ export const AppRoutes = createExtension({ name: 'routes', attachTo: { id: 'app/layout', input: 'content' }, inputs: { - routes: createExtensionInput({ - path: coreExtensionData.routePath, - ref: coreExtensionData.routeRef.optional(), - element: coreExtensionData.reactElement, - }), - }, - output: { - element: coreExtensionData.reactElement, + routes: createExtensionInput([ + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + coreExtensionData.reactElement, + ]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { const Routes = () => { const NotFoundErrorPage = useComponentRef( @@ -46,8 +44,8 @@ export const AppRoutes = createExtension({ const element = useRoutes([ ...inputs.routes.map(route => ({ - path: `${route.output.path}/*`, - element: route.output.element, + path: `${route.get(coreExtensionData.routePath)}/*`, + element: route.get(coreExtensionData.reactElement), })), { path: '*', @@ -58,8 +56,6 @@ export const AppRoutes = createExtension({ return element; }; - return { - element: , - }; + return [coreExtensionData.reactElement()]; }, }); diff --git a/packages/frontend-app-api/src/extensions/elements.tsx b/packages/frontend-app-api/src/extensions/elements.tsx index 0a764583bb..1ab7f97ba2 100644 --- a/packages/frontend-app-api/src/extensions/elements.tsx +++ b/packages/frontend-app-api/src/extensions/elements.tsx @@ -15,31 +15,36 @@ */ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; -import { - createAppRootElementExtension, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; +import { AppRootElementBlueprint } from '@backstage/frontend-plugin-api'; import React from 'react'; -export const oauthRequestDialogAppRootElement = createAppRootElementExtension({ +export const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({ namespace: 'app', name: 'oauth-request-dialog', - element: , + params: { + element: , + }, }); -export const alertDisplayAppRootElement = createAppRootElementExtension({ - namespace: 'app', - name: 'alert-display', - configSchema: createSchemaFromZod(z => - z.object({ - transientTimeoutMs: z.number().default(5000), - anchorOrigin: z - .object({ - vertical: z.enum(['top', 'bottom']).default('top'), - horizontal: z.enum(['left', 'center', 'right']).default('center'), - }) - .default({}), - }), - ), - element: ({ config }) => , -}); +export const alertDisplayAppRootElement = + AppRootElementBlueprint.makeWithOverrides({ + namespace: 'app', + name: 'alert-display', + config: { + schema: { + transientTimeoutMs: z => z.number().default(5000), + anchorOrigin: z => + z + .object({ + vertical: z.enum(['top', 'bottom']).default('top'), + horizontal: z.enum(['left', 'center', 'right']).default('center'), + }) + .default({}), + }, + }, + factory: (originalFactory, { config }) => { + return originalFactory({ + element: () => , + }); + }, + }); diff --git a/packages/frontend-app-api/src/extensions/themes.tsx b/packages/frontend-app-api/src/extensions/themes.tsx index ea11b6a638..a4fd0b253e 100644 --- a/packages/frontend-app-api/src/extensions/themes.tsx +++ b/packages/frontend-app-api/src/extensions/themes.tsx @@ -21,24 +21,34 @@ import { } from '@backstage/theme'; import DarkIcon from '@material-ui/icons/Brightness2'; import LightIcon from '@material-ui/icons/WbSunny'; -import { createThemeExtension } from '@backstage/frontend-plugin-api'; +import { ThemeBlueprint } from '@backstage/frontend-plugin-api'; -export const LightTheme = createThemeExtension({ - id: 'light', - title: 'Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), +export const LightTheme = ThemeBlueprint.make({ + name: 'light', + params: { + theme: { + id: 'light', + title: 'Light Theme', + variant: 'light', + icon: , + Provider: ({ children }) => ( + + ), + }, + }, }); -export const DarkTheme = createThemeExtension({ - id: 'dark', - title: 'Dark Theme', - variant: 'dark', - icon: , - Provider: ({ children }) => ( - - ), +export const DarkTheme = ThemeBlueprint.make({ + name: 'dark', + params: { + theme: { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + icon: , + Provider: ({ children }) => ( + + ), + }, + }, }); diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index c22cb6ff1d..8a54f3e73e 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -50,22 +50,23 @@ function createTestExtension(options: { attachTo: options.parent ? { id: `test/${options.parent}`, input: 'children' } : { id: 'app/routes', input: 'routes' }, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), - }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ], inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), + children: createExtensionInput([coreExtensionData.reactElement]), }, - factory() { - return { - path: options.path, - routeRef: options.routeRef, - element: React.createElement('div'), - }; + *factory() { + if (options.path) { + yield coreExtensionData.routePath(options.path); + } + if (options.routeRef) { + yield coreExtensionData.routeRef(options.routeRef); + } + + yield coreExtensionData.reactElement(React.createElement('div')); }, }); } diff --git a/packages/frontend-app-api/src/tree/createAppTree.test.ts b/packages/frontend-app-api/src/tree/createAppTree.test.ts index f24f199d5d..f5df5270cd 100644 --- a/packages/frontend-app-api/src/tree/createAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/createAppTree.test.ts @@ -25,8 +25,8 @@ import { createAppTree } from './createAppTree'; const extBase = { id: 'test', attachTo: { id: 'app', input: 'root' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }; describe('createAppTree', () => { @@ -60,8 +60,8 @@ describe('createAppTree', () => { name: 'app', attachTo: { id: 'app/routes', input: 'route' }, inputs: {}, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }), diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts index d9b65555fe..740f05d5f7 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -23,8 +23,8 @@ const extension = resolveExtensionDefinition( createExtension({ name: 'test', attachTo: { id: 'nonexistent', input: 'nonexistent' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ) as Extension; diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index ead9422a13..c79d8eed76 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -20,9 +20,9 @@ import { coreExtensionData, createExtension, createExtensionOverrides, - createPageExtension, + PageBlueprint, createFrontendPlugin, - createThemeExtension, + ThemeBlueprint, } from '@backstage/frontend-plugin-api'; import { screen, waitFor } from '@testing-library/react'; import { CreateAppFeatureLoader, createApp } from './createApp'; @@ -47,11 +47,16 @@ describe('createApp', () => { createFrontendPlugin({ id: 'test', extensions: [ - createThemeExtension({ - id: 'derp', - title: 'Derp', - variant: 'dark', - Provider: () =>
    Derp
    , + ThemeBlueprint.make({ + name: 'derp', + params: { + theme: { + id: 'derp', + title: 'Derp', + variant: 'dark', + Provider: () =>
    Derp
    , + }, + }, }), ], }), @@ -71,18 +76,22 @@ describe('createApp', () => { createFrontendPlugin({ id: duplicatedFeatureId, extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () =>
    First Page
    , + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
    First Page
    , + }, }), ], }), createFrontendPlugin({ id: duplicatedFeatureId, extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () =>
    Last Page
    , + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
    Last Page
    , + }, }), ], }), @@ -110,9 +119,11 @@ describe('createApp', () => { createFrontendPlugin({ id: 'test', extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () =>
    {config.getString('key')}
    , + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
    {config.getString('key')}
    , + }, }), ], }), @@ -170,7 +181,7 @@ describe('createApp', () => { createExtension({ name: 'first', attachTo: { id: 'app', input: 'root' }, - output: { element: coreExtensionData.reactElement }, + output: [coreExtensionData.reactElement], factory() { const Component = () => { const flagsApi = useApi(featureFlagsApiRef); @@ -184,7 +195,7 @@ describe('createApp', () => {
    ); }; - return { element: }; + return [coreExtensionData.reactElement()]; }, }), ], @@ -197,8 +208,8 @@ describe('createApp', () => { name: 'root', attachTo: { id: 'app', input: 'root' }, disabled: true, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }), @@ -221,14 +232,16 @@ describe('createApp', () => { createFrontendPlugin({ id: 'my-plugin', extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () => { - const Component = () => { - appTreeApi = useApi(appTreeApiRef); - return
    My Plugin Page
    ; - }; - return ; + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () => { + const Component = () => { + appTreeApi = useApi(appTreeApiRef); + return
    My Plugin Page
    ; + }; + return ; + }, }, }), ], @@ -250,7 +263,7 @@ describe('createApp', () => { content [ routes [ - + ] ] diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 6f23f4cf2c..ca42dce3e4 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -17,18 +17,18 @@ import React, { JSX, ReactNode } from 'react'; import { ConfigReader } from '@backstage/config'; import { + ApiBlueprint, AppTree, appTreeApiRef, componentsApiRef, coreExtensionData, - createApiExtension, - createThemeExtension, - createTranslationExtension, FrontendFeature, IconBundleBlueprint, iconsApiRef, RouteResolutionApi, routeResolutionApiRef, + ThemeBlueprint, + TranslationBlueprint, } from '@backstage/frontend-plugin-api'; import { App } from '../extensions/App'; import { AppRoutes } from '../extensions/AppRoutes'; @@ -110,7 +110,9 @@ import { stringifyError } from '@backstage/errors'; import { icons as defaultIcons } from '../../../app-defaults/src/defaults'; import { getBasePath } from '../routing/getBasePath'; -const DefaultApis = defaultApis.map(factory => createApiExtension({ factory })); +const DefaultApis = defaultApis.map(factory => + ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), +); export const builtinExtensions = [ App, @@ -357,23 +359,21 @@ function createApiHolder( const pluginApis = tree.root.edges.attachments .get('apis') - ?.map(e => e.instance?.getData(createApiExtension.factoryDataRef)) + ?.map(e => e.instance?.getData(ApiBlueprint.dataRefs.factory)) .filter((x): x is AnyApiFactory => !!x) ?? []; const themeExtensions = tree.root.edges.attachments .get('themes') - ?.map(e => e.instance?.getData(createThemeExtension.themeDataRef)) + ?.map(e => e.instance?.getData(ThemeBlueprint.dataRefs.theme)) .filter((x): x is AppTheme => !!x) ?? []; const translationResources = tree.root.edges.attachments .get('translations') - ?.map(e => - e.instance?.getData(createTranslationExtension.translationDataRef), - ) + ?.map(e => e.instance?.getData(TranslationBlueprint.dataRefs.translation)) .filter( - (x): x is typeof createTranslationExtension.translationDataRef.T => !!x, + (x): x is typeof TranslationBlueprint.dataRefs.translation.T => !!x, ) ?? []; const extensionIcons = tree.root.edges.attachments From 9afc8a6000f88fad84547d5843408b2d019ab837 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 09:49:06 +0200 Subject: [PATCH 277/393] chore: updating the frontend-* packages Signed-off-by: blam --- .../src/components/ExtensionBoundary.test.tsx | 31 ++--- .../src/app/createExtensionTester.test.tsx | 110 ++++++++---------- .../src/app/renderInTestApp.tsx | 18 +-- 3 files changed, 78 insertions(+), 81 deletions(-) diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index 1f12f127e8..403d591db1 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -30,24 +30,26 @@ import { } from '@backstage/core-plugin-api'; import { createRouteRef } from '../routing'; import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { createApiExtension } from '../extensions'; +import { ApiBlueprint } from '../blueprints'; const wrapInBoundaryExtension = (element?: JSX.Element) => { const routeRef = createRouteRef(); return createExtension({ name: 'test', attachTo: { id: 'app/routes', input: 'routes' }, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + ], factory({ node }) { - return { - routeRef, - path: '/', - element: {element}, - }; + return [ + coreExtensionData.reactElement( + {element}, + ), + coreExtensionData.routePath('/'), + coreExtensionData.routeRef(routeRef), + ]; }, }); }; @@ -134,8 +136,11 @@ describe('ExtensionBoundary', () => { await act(async () => { createExtensionTester(wrapInBoundaryExtension()) .add( - createApiExtension({ - factory: createApiFactory(analyticsApiRef, analyticsApiMock), + ApiBlueprint.make({ + namespace: analyticsApiRef.id, + params: { + factory: createApiFactory(analyticsApiRef, analyticsApiMock), + }, }), ) .render(); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 73220419b7..86953df675 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -41,8 +41,8 @@ describe('createExtensionTester', () => { const defaultDefinition = { namespace: 'test', attachTo: { id: 'ignored', input: 'ignored' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
    test
    }), + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
    test
    )], }; it('should render a simple extension', async () => { @@ -77,23 +77,23 @@ describe('createExtensionTester', () => { it('should render multiple extensions', async () => { const indexPageExtension = createExtension({ ...defaultDefinition, - factory: () => ({ - element: ( + factory: () => [ + coreExtensionData.reactElement(
    Index page See details -
    +
    , ), - }), + ], }); const detailsPageExtension = createExtension({ ...defaultDefinition, name: 'details', attachTo: { id: 'app/routes', input: 'routes' }, - output: { - path: coreExtensionData.routePath, - element: coreExtensionData.reactElement, - }, - factory: () => ({ path: '/details', element:
    Details page
    }), + output: [coreExtensionData.routePath, coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.routePath('/details'), + coreExtensionData.reactElement(
    Details page
    ), + ], }); const tester = createExtensionTester(indexPageExtension); @@ -112,9 +112,11 @@ describe('createExtensionTester', () => { it('should accepts a custom config', async () => { const indexPageExtension = createExtension({ ...defaultDefinition, - configSchema: createSchemaFromZod(z => - z.object({ title: z.string().optional() }), - ), + config: { + schema: { + title: z => z.string().optional(), + }, + }, factory: ({ config }) => { const Component = () => { const configApi = useApi(configApiRef); @@ -127,9 +129,8 @@ describe('createExtensionTester', () => {
    ); }; - return { - element: , - }; + + return [coreExtensionData.reactElement()]; }, }); @@ -137,17 +138,18 @@ describe('createExtensionTester', () => { ...defaultDefinition, name: 'details', attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: createSchemaFromZod(z => - z.object({ title: z.string().optional() }), - ), - output: { - path: coreExtensionData.routePath, - element: coreExtensionData.reactElement, + config: { + schema: { + title: z => z.string().optional(), + }, }, - factory: ({ config }) => ({ - path: '/details', - element:
    {config.title ?? 'Details page'}
    , - }), + output: [coreExtensionData.routePath, coreExtensionData.reactElement], + factory: ({ config }) => [ + coreExtensionData.routePath('/details'), + coreExtensionData.reactElement( +
    {config.title ?? 'Details page'}
    , + ), + ], }); const tester = createExtensionTester(indexPageExtension, { @@ -209,9 +211,7 @@ describe('createExtensionTester', () => { ); }; - return { - element: , - }; + return [coreExtensionData.reactElement()]; }, }); @@ -254,16 +254,16 @@ describe('createExtensionTester', () => { namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const extension2 = createExtension({ namespace: 'test', name: 'e2', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension); @@ -278,16 +278,16 @@ describe('createExtensionTester', () => { namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const extension2 = createExtension({ namespace: 'test', name: 'e2', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension).add(extension2); @@ -377,26 +377,21 @@ describe('createExtensionTester', () => { namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, + output: [stringDataRef], inputs: { - input: createExtensionInput( - { - output: stringDataRef, - }, - { singleton: true }, - ), + input: createExtensionInput([stringDataRef], { singleton: true }), }, - factory: ({ inputs }) => ({ - text: `nest-${inputs.input.output.output}`, - }), + factory: ({ inputs }) => [ + stringDataRef(`nest-${inputs.input.get(stringDataRef)}`), + ], }); const extension2 = createExtension({ namespace: 'test', name: 'e2', attachTo: { id: 'test/e1', input: 'blob' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension).add(extension2); @@ -418,18 +413,13 @@ describe('createExtensionTester', () => { namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, + output: [stringDataRef], inputs: { - input: createExtensionInput( - { - output: stringDataRef, - }, - { singleton: true }, - ), + input: createExtensionInput([stringDataRef], { singleton: true }), }, - factory: ({ inputs }) => ({ - text: `nest-${inputs.input.output.output}`, - }), + factory: ({ inputs }) => [ + stringDataRef(`nest-${inputs.input.get(stringDataRef)}`), + ], }); const tester = createExtensionTester(extension, { diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index dd03cdd2a2..b37a7c2a8d 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -152,14 +152,16 @@ export function renderInTestApp( kind: 'test-route', name: path, attachTo: { id: 'app/root', input: 'elements' }, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef, - }, - factory() { - return { element: , path, routeRef }; - }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef, + ], + factory: () => [ + coreExtensionData.reactElement(), + coreExtensionData.routePath(path), + coreExtensionData.routeRef(routeRef), + ], }), ); } From 7ba66a38cbf2d705c15c2fc3b52f6a65671ad635 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 09:57:57 +0200 Subject: [PATCH 278/393] chore: some other deprecations Signed-off-by: blam --- .../app-next-example-plugin/src/plugin.tsx | 10 +- plugins/api-docs/src/alpha.tsx | 300 ++++++++++-------- plugins/home/src/alpha.tsx | 39 +-- plugins/kubernetes/src/alpha/pages.tsx | 18 +- 4 files changed, 199 insertions(+), 168 deletions(-) diff --git a/packages/app-next-example-plugin/src/plugin.tsx b/packages/app-next-example-plugin/src/plugin.tsx index 7c41405c59..818e8abc84 100644 --- a/packages/app-next-example-plugin/src/plugin.tsx +++ b/packages/app-next-example-plugin/src/plugin.tsx @@ -16,13 +16,15 @@ import React from 'react'; import { - createPageExtension, + PageBlueprint, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; -export const ExamplePage = createPageExtension({ - defaultPath: '/example', - loader: () => import('./Component').then(m => ), +export const ExamplePage = PageBlueprint.make({ + params: { + defaultPath: '/example', + loader: () => import('./Component').then(m => ), + }, }); /** @public */ diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 4d803b3991..216666be8f 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -18,12 +18,11 @@ import React from 'react'; import Grid from '@material-ui/core/Grid'; import { - createApiExtension, + ApiBlueprint, + NavItemBlueprint, + PageBlueprint, createApiFactory, - createNavItemExtension, - createPageExtension, createFrontendPlugin, - createSchemaFromZod, } from '@backstage/frontend-plugin-api'; import { @@ -31,10 +30,6 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { - createEntityCardExtension, - createEntityContentExtension, -} from '@backstage/plugin-catalog-react/alpha'; import { ApiEntity, parseEntityRef, @@ -46,161 +41,190 @@ import { rootRoute, registerComponentRouteRef } from './routes'; import { apiDocsConfigRef } from './config'; import { AppIcon } from '@backstage/core-components'; -const apiDocsNavItem = createNavItemExtension({ - title: 'APIs', - routeRef: convertLegacyRouteRef(rootRoute), - icon: () => compatWrapper(), -}); +import { + EntityCardBlueprint, + EntityContentBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; -const apiDocsConfigApi = createApiExtension({ - factory: createApiFactory({ - api: apiDocsConfigRef, - deps: {}, - factory: () => { - const definitionWidgets = defaultDefinitionWidgets(); - return { - getApiDefinitionWidget: (apiEntity: ApiEntity) => { - return definitionWidgets.find(d => d.type === apiEntity.spec.type); - }, - }; - }, - }), -}); - -const apiDocsExplorerPage = createPageExtension({ - defaultPath: '/api-docs', - routeRef: convertLegacyRouteRef(rootRoute), - // Mapping DefaultApiExplorerPageProps to config - configSchema: createSchemaFromZod(z => - z.object({ - path: z.string().default('/api-docs'), - initiallySelectedFilter: z.enum(['owned', 'starred', 'all']).optional(), - // Ommiting columns and actions for now as their types are too complex to map to zod - }), - ), - loader: ({ config }) => - import('./components/ApiExplorerPage').then(m => - compatWrapper( - , - ), - ), -}); - -const apiDocsHasApisEntityCard = createEntityCardExtension({ - name: 'has-apis', - // Ommiting configSchema for now - // We are skipping variants and columns are too complex to map to zod - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: entity => { - return ( - entity.kind === 'Component' && - entity.relations?.some( - ({ type, targetRef }) => - type.toLocaleLowerCase('en-US') === RELATION_HAS_PART && - parseEntityRef(targetRef).kind === 'API', - )!! - ); +const apiDocsNavItem = NavItemBlueprint.make({ + params: { + title: 'APIs', + routeRef: convertLegacyRouteRef(rootRoute), + icon: () => compatWrapper(), }, - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), }); -const apiDocsDefinitionEntityCard = createEntityCardExtension({ +const apiDocsConfigApi = ApiBlueprint.make({ + namespace: 'api-docs', + params: { + factory: createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const definitionWidgets = defaultDefinitionWidgets(); + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + }, + }), + }, +}); + +const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ + config: { + schema: { + // Ommiting columns and actions for now as their types are too complex to map to zod + initiallySelectedFilter: z => + z.enum(['owned', 'starred', 'all']).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + defaultPath: '/api-docs', + routeRef: convertLegacyRouteRef(rootRoute), + loader: () => + import('./components/ApiExplorerPage').then(m => + compatWrapper( + , + ), + ), + }); + }, +}); + +const apiDocsHasApisEntityCard = EntityCardBlueprint.make({ + name: 'has-apis', + params: { + // Ommiting configSchema for now + // We are skipping variants and columns are too complex to map to zod + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: entity => { + return ( + entity.kind === 'Component' && + entity.relations?.some( + ({ type, targetRef }) => + type.toLocaleLowerCase('en-US') === RELATION_HAS_PART && + parseEntityRef(targetRef).kind === 'API', + )!! + ); + }, + loader: () => + import('./components/ApisCards').then(m => + compatWrapper(), + ), + }, +}); + +const apiDocsDefinitionEntityCard = EntityCardBlueprint.make({ name: 'definition', - filter: 'kind:api', - loader: () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:api', + loader: () => + import('./components/ApiDefinitionCard').then(m => + compatWrapper(), + ), + }, }); -const apiDocsConsumedApisEntityCard = createEntityCardExtension({ +const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({ name: 'consumed-apis', - // Ommiting configSchema for now - // We are skipping variants and columns are too complex to map to zod - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:component', - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants and columns are too complex to map to zod + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:component', + loader: () => + import('./components/ApisCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsProvidedApisEntityCard = createEntityCardExtension({ +const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({ name: 'provided-apis', - // Ommiting configSchema for now - // We are skipping variants and columns are too complex to map to zod - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:component', - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants and columns are too complex to map to zod + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:component', + loader: () => + import('./components/ApisCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsConsumingComponentsEntityCard = createEntityCardExtension({ +const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({ name: 'consuming-components', - // Ommiting configSchema for now - // We are skipping variants - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:api', - loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:api', + loader: () => + import('./components/ComponentsCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsProvidingComponentsEntityCard = createEntityCardExtension({ +const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({ name: 'providing-components', - // Ommiting configSchema for now - // We are skipping variants - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:api', - loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:api', + loader: () => + import('./components/ComponentsCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsDefinitionEntityContent = createEntityContentExtension({ +const apiDocsDefinitionEntityContent = EntityContentBlueprint.make({ name: 'definition', - defaultPath: '/defintion', - defaultTitle: 'Definition', - filter: 'kind:api', - loader: async () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper( - - - - - , + params: { + defaultPath: '/defintion', + defaultTitle: 'Definition', + filter: 'kind:api', + loader: async () => + import('./components/ApiDefinitionCard').then(m => + compatWrapper( + + + + + , + ), ), - ), + }, }); -const apiDocsApisEntityContent = createEntityContentExtension({ +const apiDocsApisEntityContent = EntityContentBlueprint.make({ name: 'apis', - defaultPath: '/apis', - defaultTitle: 'APIs', - filter: 'kind:component', - loader: async () => - import('./components/ApisCards').then(m => - compatWrapper( - - - - - - - - , + params: { + defaultPath: '/apis', + defaultTitle: 'APIs', + filter: 'kind:component', + loader: async () => + import('./components/ApisCards').then(m => + compatWrapper( + + + + + + + + , + ), ), - ), + }, }); export default createFrontendPlugin({ diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index e31fe833f6..ce96b18943 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -20,7 +20,7 @@ import { coreExtensionData, createExtensionDataRef, createExtensionInput, - createPageExtension, + PageBlueprint, createFrontendPlugin, createRouteRef, } from '@backstage/frontend-plugin-api'; @@ -35,31 +35,34 @@ export const titleExtensionDataRef = createExtensionDataRef().with({ id: 'title', }); -const homePage = createPageExtension({ - defaultPath: '/home', - routeRef: rootRouteRef, +const homePage = PageBlueprint.makeWithOverrides({ inputs: { props: createExtensionInput( - { - children: coreExtensionData.reactElement.optional(), - title: titleExtensionDataRef.optional(), - }, - + [ + coreExtensionData.reactElement.optional(), + titleExtensionDataRef.optional(), + ], { singleton: true, optional: true, }, ), }, - loader: ({ inputs }) => - import('./components/').then(m => - compatWrapper( - , - ), - ), + factory: (originalFactory, { inputs }) => { + return originalFactory({ + defaultPath: '/home', + routeRef: rootRouteRef, + loader: () => + import('./components/').then(m => + compatWrapper( + , + ), + ), + }); + }, }); /** diff --git a/plugins/kubernetes/src/alpha/pages.tsx b/plugins/kubernetes/src/alpha/pages.tsx index 8b5db150bd..a58ffec94f 100644 --- a/plugins/kubernetes/src/alpha/pages.tsx +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -16,18 +16,20 @@ import React from 'react'; // Add this line to import React -import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { rootCatalogKubernetesRouteRef } from '../plugin'; -export const kubernetesPage = createPageExtension({ - defaultPath: '/kubernetes', - // you can reuse the existing routeRef - // by wrapping into the convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), - // these inputs usually match the props required by the component. - loader: () => import('../Router').then(m => compatWrapper()), +export const kubernetesPage = PageBlueprint.make({ + params: { + defaultPath: '/kubernetes', + // you can reuse the existing routeRef + // by wrapping into the convertLegacyRouteRef. + routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), + // these inputs usually match the props required by the component. + loader: () => import('../Router').then(m => compatWrapper()), + }, }); From 71519b2ddc997af03a626b3ad9fc6dc400b23e9b Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 10:17:55 +0200 Subject: [PATCH 279/393] chore: fix typescript Signed-off-by: blam --- packages/core-compat-api/src/collectLegacyRoutes.tsx | 2 -- .../frontend-test-utils/src/app/createExtensionTester.test.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 68e9aaf3a1..9cc23b8c29 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -24,10 +24,8 @@ import { BackstagePlugin, ExtensionDefinition, coreExtensionData, - createApiExtension, createExtension, createExtensionInput, - createPageExtension, createFrontendPlugin, ApiBlueprint, PageBlueprint, diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 86953df675..5102649059 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -26,7 +26,6 @@ import { createExtension, createExtensionDataRef, createExtensionInput, - createSchemaFromZod, useAnalytics, useApi, } from '@backstage/frontend-plugin-api'; From 549d5502ccd30eda816986d87acf961698b023a9 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 10:38:45 +0200 Subject: [PATCH 280/393] chore: updating api-reports Signed-off-by: blam --- .../app-next-example-plugin/api-report.md | 36 +- plugins/api-docs/api-report-alpha.md | 349 +++++++++++++++++- plugins/home/api-report-alpha.md | 62 +++- plugins/kubernetes/api-report-alpha.md | 30 +- 4 files changed, 473 insertions(+), 4 deletions(-) diff --git a/packages/app-next-example-plugin/api-report.md b/packages/app-next-example-plugin/api-report.md index 962b1473a8..3c8b740e1c 100644 --- a/packages/app-next-example-plugin/api-report.md +++ b/packages/app-next-example-plugin/api-report.md @@ -3,11 +3,45 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const examplePlugin: BackstagePlugin<{}, {}, {}>; +const examplePlugin: BackstagePlugin< + {}, + {}, + { + 'page:example': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + undefined + >; + } +>; export default examplePlugin; // @public (undocumented) diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md index 6003056cb3..1732f06ff6 100644 --- a/plugins/api-docs/api-report-alpha.md +++ b/plugins/api-docs/api-report-alpha.md @@ -3,8 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) @@ -15,7 +24,345 @@ const _default: BackstagePlugin< { registerApi: ExternalRouteRef; }, - {} + { + 'nav-item:api-docs': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + 'api:api-docs': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + 'api-docs', + undefined + >; + 'page:api-docs': ExtensionDefinition< + { + initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined; + } & { + path: string | undefined; + }, + { + initiallySelectedFilter?: 'all' | 'owned' | 'starred' | undefined; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'page', + undefined, + undefined + >; + 'entity-card:api-docs/has-apis': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'has-apis' + >; + 'entity-card:api-docs/definition': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'definition' + >; + 'entity-card:api-docs/consumed-apis': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'consumed-apis' + >; + 'entity-card:api-docs/provided-apis': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'provided-apis' + >; + 'entity-card:api-docs/consuming-components': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'consuming-components' + >; + 'entity-card:api-docs/providing-components': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'providing-components' + >; + 'entity-content:api-docs/definition': ExtensionDefinition< + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-content', + undefined, + 'definition' + >; + 'entity-content:api-docs/apis': ExtensionDefinition< + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-content', + undefined, + 'apis' + >; + } >; export default _default; diff --git a/plugins/home/api-report-alpha.md b/plugins/home/api-report-alpha.md index 0f9b980a78..2efdc19b79 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -3,11 +3,71 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -const _default: BackstagePlugin<{}, {}, {}>; +const _default: BackstagePlugin< + {}, + {}, + { + 'page:home': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + props: ExtensionInput< + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'title', + { + optional: true; + } + >, + { + singleton: true; + optional: true; + } + >; + }, + 'page', + undefined, + undefined + >; + } +>; export default _default; // @alpha (undocumented) diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md index befbcf379a..f3260208c3 100644 --- a/plugins/kubernetes/api-report-alpha.md +++ b/plugins/kubernetes/api-report-alpha.md @@ -3,7 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// + +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) @@ -12,7 +18,29 @@ const _default: BackstagePlugin< kubernetes: RouteRef; }, {}, - {} + { + 'page:kubernetes': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + undefined + >; + } >; export default _default; From a737904cac906469ef20b0cfc8d160599aa94c21 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 11:36:19 +0200 Subject: [PATCH 281/393] chore: fix tests Signed-off-by: blam --- .../src/routing/extractRouteInfoFromAppNode.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index 8a54f3e73e..1fc899a906 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -59,10 +59,11 @@ function createTestExtension(options: { children: createExtensionInput([coreExtensionData.reactElement]), }, *factory() { - if (options.path) { + if (options.path !== undefined) { yield coreExtensionData.routePath(options.path); } - if (options.routeRef) { + + if (options.routeRef !== undefined) { yield coreExtensionData.routeRef(options.routeRef); } From 89859bc83d8cf745ddbe89001222ee08e5010826 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 14:16:18 +0200 Subject: [PATCH 282/393] chore: fixing api-docs naming Signed-off-by: blam Signed-off-by: blam --- plugins/api-docs/src/alpha.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 216666be8f..b38bfc6c91 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -55,7 +55,7 @@ const apiDocsNavItem = NavItemBlueprint.make({ }); const apiDocsConfigApi = ApiBlueprint.make({ - namespace: 'api-docs', + name: 'config', params: { factory: createApiFactory({ api: apiDocsConfigRef, From fe1fbb26ebbdfa780e3b1139e11f40123d4c0db8 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 14:36:08 +0200 Subject: [PATCH 283/393] chore: add changeset Signed-off-by: blam --- .changeset/modern-brooms-grow.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/modern-brooms-grow.md diff --git a/.changeset/modern-brooms-grow.md b/.changeset/modern-brooms-grow.md new file mode 100644 index 0000000000..0aff21dcca --- /dev/null +++ b/.changeset/modern-brooms-grow.md @@ -0,0 +1,10 @@ +--- +'@backstage/core-compat-api': patch +'@backstage/frontend-app-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-home': patch +'@backstage/plugin-kubernetes': patch +--- + +Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. From 384b7bac2eae4bcf22e879ce2f4de6d5f6ef364a Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Thu, 15 Aug 2024 07:52:34 -0500 Subject: [PATCH 284/393] Updated Prometheus and OpenTelemetry Guides Signed-off-by: Andre Wanlin --- contrib/docs/tutorials/prometheus-metrics.md | 3 +++ docs/tutorials/setup-opentelemetry.md | 22 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/contrib/docs/tutorials/prometheus-metrics.md b/contrib/docs/tutorials/prometheus-metrics.md index fb4366c7f7..0db97c649e 100644 --- a/contrib/docs/tutorials/prometheus-metrics.md +++ b/contrib/docs/tutorials/prometheus-metrics.md @@ -1,5 +1,8 @@ # Prometheus +> [!NOTE] +> The Prometheus metrics have been marked as deprecated and will be removed at a later point. The recommendation is to use the OpenTelemetry metrics by following the [Setup OpenTelemetry](https://backstage.io/docs/tutorials/setup-opentelemetry) documentation + ## Overview This is a small tutorial that goes over how to setup your Backstage instance to output metrics in a format that can be pulled in by Prometheus. diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index faaeab49f4..cba8ba7049 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -83,6 +83,28 @@ CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config" If you need to disable/configure some OpenTelemetry feature there are lots of [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) which you can tweak. +### Available Metrics + +The following metrics are available: + +- `catalog_entities_count`: Total amount of entities in the catalog +- `catalog_registered_locations_count`: Total amount of registered locations in the catalog +- `catalog_relations_count`: Total amount of relations between entities +- `catalog.processed.entities.count`: Amount of entities processed +- `catalog.processing.duration`: Time spent executing the full processing flow +- `catalog.processors.duration`: Time spent executing catalog processors +- `catalog.processing.queue.delay`: The amount of delay between being scheduled for processing, and the start of actually being processed +- `catalog.stitched.entities.count`: Amount of entities stitched +- `catalog.stitching.duration`: Time spent executing the full stitching flow +- `catalog.stitching.queue.length`: Number of entities currently in the stitching queue +- `catalog.stitching.queue.delay`: The amount of delay between being scheduled for stitching, and the start of actually being stitched +- `scaffolder.task.count`: Count of task runs +- `scaffolder.task.duration`: Duration of a task run +- `scaffolder.step.count`: Count of step runs +- `scaffolder.step.duration`: Duration of a step runs +- `backend_tasks.task.runs.count`: Total number of times a task has been run +- `backend_tasks.task.runs.duration`: Histogram of task run durations + ## References - [Getting started with OpenTelemetry Node.js](https://opentelemetry.io/docs/instrumentation/js/getting-started/nodejs/) From 2205653f0b0c71325b7d03c118e2e1a0f7339bc0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 14:55:26 +0200 Subject: [PATCH 285/393] docs/frontend-system: docs for how to override declared outputs Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index f988082258..0efa578a30 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -79,6 +79,29 @@ const myOverrideExtension = myExtension.override({ Note the `yield*` expression, which forwards all values from the provided iterable to the generator, in this case the original factory output. +## Overriding declared outputs + +When overriding an extension out can provide a new output declaration. This **replaces** any existing output declaration, which means that you want to forward any of the original output you will need to declare it again. The following example shows how to override an extension and replace the output declaration: + +```tsx +// Original extension +const exampleExtension = createExtension({ + name: 'example', + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(

    Example

    )], +}); + +// Override extension, with additional outputs +const overrideExtension = exampleExtension.override({ + output: [coreExtensionData.reactElement, coreExtensionData.routePath], + factory(originalFactory) { + return [...originalFactory(), coreExtensionData.routePath('/example')]; + }, +}); +``` + +When overriding the output declaration you don't need to include the original outputs. Just remember that you will no longer be able to directly forward the output from the original factory, and will still need to adhere to the contract of the input that the extension is attached to. + ## Override App Extensions In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. From a2bb9d2e351d400851fc0d6f59b4d56d9915e723 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 15:15:03 +0200 Subject: [PATCH 286/393] docs/frontend-system: docs for how to override declared inputs Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 0efa578a30..8b6d87bc81 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -102,6 +102,40 @@ const overrideExtension = exampleExtension.override({ When overriding the output declaration you don't need to include the original outputs. Just remember that you will no longer be able to directly forward the output from the original factory, and will still need to adhere to the contract of the input that the extension is attached to. +## Overriding declared inputs + +When overriding an extension you can also provide new input declarations. You can define any number of new inputs, but you are **not** able to override the existing inputs declared by the original extension. The new inputs will be merged with the existing ones, giving the override factory access to both. The following example shows how to override an extension and add a new input declaration: + +```tsx +const myOverrideExtension = myExtension.override({ + inputs: { + myOverrideInput: createExtensionInput([coreExtensionData.reactElement]), + }, + factory(originalFactory, { inputs }) { + const originalOutput = originalFactory(); + const originalElement = originalOutput.get(coreExtensionData.reactElement); + + return [ + ...originalOutput, + coreExtensionData.reactElement( +
    +

    Original element

    + {originalElement} +

    Additional inputs

    +
      + {inputs.myOverrideInput.map(i => ( +
    • + {i.get(coreExtensionData.reactElement)} +
    • + ))} +
    +
    , + ), + ]; + }, +}); +``` + ## Override App Extensions In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. From 5eed3b15e3df0615e93c6d751d84f813cc9f5ff5 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:19:10 +0200 Subject: [PATCH 287/393] chore: small changes Signed-off-by: blam --- packages/core-compat-api/src/collectLegacyRoutes.tsx | 2 +- .../src/routing/extractRouteInfoFromAppNode.test.ts | 2 +- .../frontend-test-utils/src/app/createExtensionTester.test.tsx | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 9cc23b8c29..c5cb13b6e1 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -90,7 +90,7 @@ function makeRoutingShimExtension(options: { coreExtensionData.routeRef.optional(), ], *factory() { - if (routePath) { + if (routePath !== undefined) { yield coreExtensionData.routePath(routePath); } diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index 1fc899a906..51d26ec51b 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -63,7 +63,7 @@ function createTestExtension(options: { yield coreExtensionData.routePath(options.path); } - if (options.routeRef !== undefined) { + if (options.routeRef) { yield coreExtensionData.routeRef(options.routeRef); } diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 5102649059..14ffd05ada 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -395,9 +395,7 @@ describe('createExtensionTester', () => { const tester = createExtensionTester(extension).add(extension2); - // @ts-expect-error expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); - // @ts-expect-error expect(tester.query(extension2).get(stringDataRef)).toBe('test-text'); // @ts-expect-error expect(tester.query(extension).input('input').data(stringDataRef)).toBe( @@ -426,7 +424,6 @@ describe('createExtensionTester', () => { inputs: { input: 'test-text' }, }); - // @ts-expect-error expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); }); }); From 648ab6764fc30c0b8daf0d73cdbbdaf106f522e2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 15:23:30 +0200 Subject: [PATCH 288/393] docs/frontend-system: docs for how to override config schema Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 8b6d87bc81..5e55ef3d0e 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -136,6 +136,40 @@ const myOverrideExtension = myExtension.override({ }); ``` +## Overriding configuration schema + +Overriding the configuration schema works very similar to overriding the declared inputs. You can define new configuration fields that will be merged with the existing ones, but you can not re-declare existing fields. The following example shows how to override an extension and add a new configuration field: + +```tsx +// Original extension +const exampleExtension = createExtension({ + name: 'example', + config: { + schema: { + layout: z => z.enum(['grid', 'list']).optional(), + }, + }, + output: [coreExtensionData.reactElement], + factory: ({ config }) => [ + coreExtensionData.reactElement(), + ], +}); + +const overrideExtension = exampleExtension.override({ + config: { + schema: { + additionalField: z => z.string().optional(), + }, + }, + factory(originalFactory, { config }) { + console.log( + `additionalField=${config.additionalField} layout=${config.layout}`, + ); + return originalFactory(); + }, +}); +``` + ## Override App Extensions In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. From 80769d9f0c97b2db25ef302fcd95ce74e44276e2 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:31:24 +0200 Subject: [PATCH 289/393] chore: fix alpha api reports Signed-off-by: blam --- plugins/api-docs/api-report-alpha.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md index 1732f06ff6..e0831b2d98 100644 --- a/plugins/api-docs/api-report-alpha.md +++ b/plugins/api-docs/api-report-alpha.md @@ -42,14 +42,14 @@ const _default: BackstagePlugin< undefined, undefined >; - 'api:api-docs': ExtensionDefinition< + 'api:api-docs/config': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, 'api', - 'api-docs', - undefined + undefined, + 'config' >; 'page:api-docs': ExtensionDefinition< { From 8cb9a85596a5417a004811ffa429527b17ce9b72 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:21:03 +0200 Subject: [PATCH 290/393] chore: migrate rest of catalogt Signed-off-by: blam --- plugins/catalog/src/alpha/apis.tsx | 60 ++++--- .../createHasMatcher.test.ts | 0 .../createHasMatcher.ts | 0 .../createIsMatcher.test.ts | 0 .../createIsMatcher.ts | 0 .../createKindMatcher.test.ts | 0 .../createKindMatcher.ts | 0 .../createTypeMatcher.test.ts | 0 .../createTypeMatcher.ts | 0 .../filter/{matrchers => matchers}/types.ts | 0 .../src/alpha/filter/parseFilterExpression.ts | 10 +- plugins/catalog/src/alpha/filters.tsx | 146 +++++++++++------- plugins/catalog/src/alpha/navItems.tsx | 12 +- plugins/catalog/src/alpha/pages.tsx | 108 +++++++------ .../catalog/src/alpha/searchResultItems.tsx | 16 +- 15 files changed, 204 insertions(+), 148 deletions(-) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createHasMatcher.test.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createHasMatcher.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createIsMatcher.test.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createIsMatcher.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createKindMatcher.test.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createKindMatcher.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createTypeMatcher.test.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/createTypeMatcher.ts (100%) rename plugins/catalog/src/alpha/filter/{matrchers => matchers}/types.ts (100%) diff --git a/plugins/catalog/src/alpha/apis.tsx b/plugins/catalog/src/alpha/apis.tsx index 92880ba728..937ba118b3 100644 --- a/plugins/catalog/src/alpha/apis.tsx +++ b/plugins/catalog/src/alpha/apis.tsx @@ -21,7 +21,10 @@ import { storageApiRef, } from '@backstage/core-plugin-api'; import { CatalogClient } from '@backstage/catalog-client'; -import { createApiExtension } from '@backstage/frontend-plugin-api'; +import { + ApiBlueprint, + createApiExtension, +} from '@backstage/frontend-plugin-api'; import { catalogApiRef, entityPresentationApiRef, @@ -32,33 +35,42 @@ import { DefaultStarredEntitiesApi, } from '../apis'; -export const catalogApi = createApiExtension({ - factory: createApiFactory({ - api: catalogApiRef, - deps: { - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ discoveryApi, fetchApi }) => - new CatalogClient({ discoveryApi, fetchApi }), - }), +export const catalogApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: catalogApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new CatalogClient({ discoveryApi, fetchApi }), + }), + }, }); -export const catalogStarredEntitiesApi = createApiExtension({ - factory: createApiFactory({ - api: starredEntitiesApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }), - }), +export const catalogStarredEntitiesApi = ApiBlueprint.make({ + name: 'starred-entities', + params: { + factory: createApiFactory({ + api: starredEntitiesApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => + new DefaultStarredEntitiesApi({ storageApi }), + }), + }, }); -export const entityPresentationApi = createApiExtension({ - factory: createApiFactory({ - api: entityPresentationApiRef, - deps: { catalogApiImp: catalogApiRef }, - factory: ({ catalogApiImp }) => - DefaultEntityPresentationApi.create({ catalogApi: catalogApiImp }), - }), +export const entityPresentationApi = ApiBlueprint.make({ + name: 'entity-presentation', + params: { + factory: createApiFactory({ + api: entityPresentationApiRef, + deps: { catalogApiImp: catalogApiRef }, + factory: ({ catalogApiImp }) => + DefaultEntityPresentationApi.create({ catalogApi: catalogApiImp }), + }), + }, }); export default [catalogApi, catalogStarredEntitiesApi, entityPresentationApi]; diff --git a/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createHasMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createHasMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createHasMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createHasMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createIsMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createIsMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createIsMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createIsMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createKindMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createKindMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createKindMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createKindMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/types.ts b/plugins/catalog/src/alpha/filter/matchers/types.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/types.ts rename to plugins/catalog/src/alpha/filter/matchers/types.ts diff --git a/plugins/catalog/src/alpha/filter/parseFilterExpression.ts b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts index a4c17cb8da..4f809690f7 100644 --- a/plugins/catalog/src/alpha/filter/parseFilterExpression.ts +++ b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts @@ -16,11 +16,11 @@ import { Entity } from '@backstage/catalog-model'; import { InputError } from '@backstage/errors'; -import { EntityMatcherFn } from './matrchers/types'; -import { createKindMatcher } from './matrchers/createKindMatcher'; -import { createTypeMatcher } from './matrchers/createTypeMatcher'; -import { createIsMatcher } from './matrchers/createIsMatcher'; -import { createHasMatcher } from './matrchers/createHasMatcher'; +import { EntityMatcherFn } from './matchers/types'; +import { createKindMatcher } from './matchers/createKindMatcher'; +import { createTypeMatcher } from './matchers/createTypeMatcher'; +import { createIsMatcher } from './matchers/createIsMatcher'; +import { createHasMatcher } from './matchers/createHasMatcher'; const rootMatcherFactories: Record< string, diff --git a/plugins/catalog/src/alpha/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index 62cf9a1353..f4d58e8273 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -15,97 +15,123 @@ */ import React from 'react'; -import { createCatalogFilterExtension } from './createCatalogFilterExtension'; import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; +import { CatalogFilterBlueprint } from './blueprints'; -const catalogTagCatalogFilter = createCatalogFilterExtension({ +const catalogTagCatalogFilter = CatalogFilterBlueprint.make({ name: 'tag', - loader: async () => { - const { EntityTagPicker } = await import('@backstage/plugin-catalog-react'); - return ; + params: { + loader: async () => { + const { EntityTagPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogKindCatalogFilter = createCatalogFilterExtension({ +const catalogKindCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'kind', - configSchema: createSchemaFromZod(z => - z.object({ - initialFilter: z.string().default('component'), - }), - ), - loader: async ({ config }) => { - const { EntityKindPicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + config: { + schema: { + initialFilter: z => z.string().default('component'), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => { + const { EntityKindPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }); }, }); -const catalogTypeCatalogFilter = createCatalogFilterExtension({ +const catalogTypeCatalogFilter = CatalogFilterBlueprint.make({ name: 'type', - loader: async () => { - const { EntityTypePicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityTypePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogModeCatalogFilter = createCatalogFilterExtension({ +const catalogModeCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'mode', - configSchema: createSchemaFromZod(z => - z.object({ - mode: z.enum(['owners-only', 'all']).optional(), - }), - ), - loader: async ({ config }) => { - const { EntityOwnerPicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + config: { + schema: { + mode: z => z.enum(['owners-only', 'all']).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => { + const { EntityOwnerPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }); }, }); -const catalogNamespaceCatalogFilter = createCatalogFilterExtension({ +const catalogNamespaceCatalogFilter = CatalogFilterBlueprint.make({ name: 'namespace', - loader: async () => { - const { EntityNamespacePicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityNamespacePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogLifecycleCatalogFilter = createCatalogFilterExtension({ +const catalogLifecycleCatalogFilter = CatalogFilterBlueprint.make({ name: 'lifecycle', - loader: async () => { - const { EntityLifecyclePicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityLifecyclePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogProcessingStatusCatalogFilter = createCatalogFilterExtension({ +const catalogProcessingStatusCatalogFilter = CatalogFilterBlueprint.make({ name: 'processing-status', - loader: async () => { - const { EntityProcessingStatusPicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityProcessingStatusPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogListCatalogFilter = createCatalogFilterExtension({ +const catalogListCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'list', - configSchema: createSchemaFromZod(z => - z.object({ - initialFilter: z.enum(['owned', 'starred', 'all']).default('owned'), - }), - ), - loader: async ({ config }) => { - const { UserListPicker } = await import('@backstage/plugin-catalog-react'); - return ; + config: { + schema: { + initialFilter: z => z.enum(['owned', 'starred', 'all']).default('owned'), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => { + const { UserListPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }); }, }); diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/catalog/src/alpha/navItems.tsx index c1360cc29f..0eb4e6827e 100644 --- a/plugins/catalog/src/alpha/navItems.tsx +++ b/plugins/catalog/src/alpha/navItems.tsx @@ -16,13 +16,15 @@ import HomeIcon from '@material-ui/icons/Home'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; -import { createNavItemExtension } from '@backstage/frontend-plugin-api'; +import { NavItemBlueprint } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from '../routes'; -export const catalogNavItem = createNavItemExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - title: 'Catalog', - icon: HomeIcon, +export const catalogNavItem = NavItemBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + title: 'Catalog', + icon: HomeIcon, + }, }); export default [catalogNavItem]; diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index 5c583e6a19..764acd1fe5 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -20,72 +20,86 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { - createPageExtension, coreExtensionData, createExtensionInput, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import { AsyncEntityProvider, entityRouteRef, } from '@backstage/plugin-catalog-react'; -import { catalogExtensionData } from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { rootRouteRef } from '../routes'; import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl'; import { buildFilterFn } from './filter/FilterWrapper'; -export const catalogPage = createPageExtension({ - defaultPath: '/catalog', - routeRef: convertLegacyRouteRef(rootRouteRef), +export const catalogPage = PageBlueprint.makeWithOverrides({ inputs: { - filters: createExtensionInput({ - element: coreExtensionData.reactElement, - }), + filters: createExtensionInput([coreExtensionData.reactElement]), }, - loader: async ({ inputs }) => { - const { BaseCatalogPage } = await import('../components/CatalogPage'); - const filters = inputs.filters.map(filter => filter.output.element); - return compatWrapper({filters}} />); + factory(originalFactory, { inputs }) { + return originalFactory({ + defaultPath: '/catalog', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: async () => { + const { BaseCatalogPage } = await import('../components/CatalogPage'); + const filters = inputs.filters.map(filter => + filter.get(coreExtensionData.reactElement), + ); + return compatWrapper({filters}} />); + }, + }); }, }); -export const catalogEntityPage = createPageExtension({ +export const catalogEntityPage = PageBlueprint.makeWithOverrides({ name: 'entity', - defaultPath: '/catalog/:namespace/:kind/:name', - routeRef: convertLegacyRouteRef(entityRouteRef), inputs: { - contents: createExtensionInput({ - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }), + contents: createExtensionInput([ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + EntityContentBlueprint.dataRefs.title, + EntityContentBlueprint.dataRefs.filterFunction.optional(), + EntityContentBlueprint.dataRefs.filterExpression.optional(), + ]), }, - loader: async ({ inputs }) => { - const { EntityLayout } = await import('../components/EntityLayout'); - const Component = () => { - return ( - - - {inputs.contents.map(({ output }) => ( - - {output.element} - - ))} - - - ); - }; - return compatWrapper(); + factory(originalFactory, { inputs }) { + return originalFactory({ + defaultPath: '/catalog/:namespace/:kind/:name', + routeRef: convertLegacyRouteRef(entityRouteRef), + loader: async () => { + const { EntityLayout } = await import('../components/EntityLayout'); + const Component = () => { + return ( + + + {inputs.contents.map(output => { + return ( + + {output.get(coreExtensionData.reactElement)} + + ); + })} + + + ); + }; + return compatWrapper(); + }, + }); }, }); diff --git a/plugins/catalog/src/alpha/searchResultItems.tsx b/plugins/catalog/src/alpha/searchResultItems.tsx index 5233144173..8e140de637 100644 --- a/plugins/catalog/src/alpha/searchResultItems.tsx +++ b/plugins/catalog/src/alpha/searchResultItems.tsx @@ -14,14 +14,16 @@ * limitations under the License. */ -import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; -export const catalogSearchResultListItem = createSearchResultListItemExtension({ - predicate: result => result.type === 'software-catalog', - component: () => - import('../components/CatalogSearchResultListItem').then( - m => m.CatalogSearchResultListItem, - ), +export const catalogSearchResultListItem = SearchResultListItemBlueprint.make({ + params: { + predicate: result => result.type === 'software-catalog', + component: () => + import('../components/CatalogSearchResultListItem').then( + m => m.CatalogSearchResultListItem, + ), + }, }); export default [catalogSearchResultListItem]; From 0f2a745ac3663a7c1991ef10049b2566385a50ad Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:21:24 +0200 Subject: [PATCH 291/393] chore: migrate catalog-graph Signed-off-by: blam Signed-off-by: blam --- plugins/catalog-graph/src/alpha.tsx | 122 +++++++++++++--------------- 1 file changed, 56 insertions(+), 66 deletions(-) diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index d42d5587ae..11c845814f 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -16,84 +16,74 @@ import React from 'react'; import { - createPageExtension, createFrontendPlugin, - createSchemaFromZod, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; import { Direction } from './components'; -function getEntityGraphRelationsConfigSchema( - z: Parameters[0]>[0], -) { - // Mapping EntityRelationsGraphProps to config - // The classname and render functions are configurable only via extension overrides - return z.object({ - kinds: z.array(z.string()).optional(), - relations: z.array(z.string()).optional(), - maxDepth: z.number().optional(), - unidirectional: z.boolean().optional(), - mergeRelations: z.boolean().optional(), - direction: z.nativeEnum(Direction).optional(), - relationPairs: z.array(z.tuple([z.string(), z.string()])).optional(), - zoom: z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), - curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), - }); -} - -const CatalogGraphEntityCard = createEntityCardExtension({ +const CatalogGraphEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'relations', - configSchema: createSchemaFromZod(z => - z - .object({ - // Filter is a config required to all entity cards - filter: z.string().optional(), - title: z.string().optional(), - height: z.number().optional(), - // Skipping a "variant" config for now, defaulting to "gridItem" in the component - // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - }) - .merge(getEntityGraphRelationsConfigSchema(z)), - ), - loader: async ({ config: { filter, ...props } }) => - import('./components/CatalogGraphCard').then(m => - compatWrapper(), - ), + config: { + schema: { + kinds: z => z.array(z.string()).optional(), + relations: z => z.array(z.string()).optional(), + maxDepth: z => z.number().optional(), + unidirectional: z => z.boolean().optional(), + mergeRelations: z => z.boolean().optional(), + direction: z => z.nativeEnum(Direction).optional(), + relationPairs: z => z.array(z.tuple([z.string(), z.string()])).optional(), + zoom: z => z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), + curve: z => z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + // Skipping a "variant" config for now, defaulting to "gridItem" in the component + // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + title: z => z.string().optional(), + height: z => z.number().optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => + import('./components/CatalogGraphCard').then(m => + compatWrapper(), + ), + }); + }, }); -const CatalogGraphPage = createPageExtension({ - defaultPath: '/catalog-graph', - routeRef: convertLegacyRouteRef(catalogGraphRouteRef), - configSchema: createSchemaFromZod(z => - z.object({ - // Path is a default config required to all pages - path: z.string().default('/catalog-graph'), - // Mapping intialState prop to config, these are the initial filter values, as opposed to configuration of the available filter values - initialState: z - .object({ - selectedKinds: z.array(z.string()).optional(), - selectedRelations: z.array(z.string()).optional(), - rootEntityRefs: z.array(z.string()).optional(), - maxDepth: z.number().optional(), - unidirectional: z.boolean().optional(), - mergeRelations: z.boolean().optional(), - direction: z.nativeEnum(Direction).optional(), - showFilters: z.boolean().optional(), - curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), - }) - .merge(getEntityGraphRelationsConfigSchema(z)) - .optional(), - }), - ), - loader: ({ config: { path, ...props } }) => - import('./components/CatalogGraphPage').then(m => - compatWrapper(), - ), +const CatalogGraphPage = PageBlueprint.makeWithOverrides({ + config: { + schema: { + selectedKinds: z => z.array(z.string()).optional(), + selectedRelations: z => z.array(z.string()).optional(), + rootEntityRefs: z => z.array(z.string()).optional(), + maxDepth: z => z.number().optional(), + unidirectional: z => z.boolean().optional(), + mergeRelations: z => z.boolean().optional(), + direction: z => z.nativeEnum(Direction).optional(), + showFilters: z => z.boolean().optional(), + curve: z => z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + kinds: z => z.array(z.string()).optional(), + relations: z => z.array(z.string()).optional(), + relationPairs: z => z.array(z.tuple([z.string(), z.string()])).optional(), + zoom: z => z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + defaultPath: '/catalog-graph', + routeRef: convertLegacyRouteRef(catalogGraphRouteRef), + loader: () => + import('./components/CatalogGraphPage').then(m => + compatWrapper(), + ), + }); + }, }); export default createFrontendPlugin({ From 1a4df7277cfc0aaca6119f440812f52fc7b67a47 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:21:41 +0200 Subject: [PATCH 292/393] chore: some more migrations Signed-off-by: blam --- plugins/scaffolder/src/alpha.tsx | 64 +++---- plugins/search/src/alpha.tsx | 290 ++++++++++++++++--------------- 2 files changed, 185 insertions(+), 169 deletions(-) diff --git a/plugins/scaffolder/src/alpha.tsx b/plugins/scaffolder/src/alpha.tsx index d1240e19e2..3136626f47 100644 --- a/plugins/scaffolder/src/alpha.tsx +++ b/plugins/scaffolder/src/alpha.tsx @@ -16,14 +16,14 @@ import React from 'react'; import { - createApiExtension, createApiFactory, - createNavItemExtension, - createPageExtension, createFrontendPlugin, discoveryApiRef, fetchApiRef, identityApiRef, + ApiBlueprint, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import { @@ -51,36 +51,42 @@ export { type TemplateWizardPageProps, } from './next'; -const scaffolderApi = createApiExtension({ - factory: createApiFactory({ - api: scaffolderApiRef, - deps: { - discoveryApi: discoveryApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - fetchApi: fetchApiRef, - identityApi: identityApiRef, - }, - factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => - new ScaffolderClient({ - discoveryApi, - scmIntegrationsApi, - fetchApi, - identityApi, - }), - }), +const scaffolderApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => + new ScaffolderClient({ + discoveryApi, + scmIntegrationsApi, + fetchApi, + identityApi, + }), + }), + }, }); -const scaffolderPage = createPageExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - defaultPath: '/create', - loader: () => - import('./components/Router').then(m => compatWrapper()), +const scaffolderPage = PageBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + defaultPath: '/create', + loader: () => + import('./components/Router').then(m => compatWrapper()), + }, }); -const scaffolderNavItem = createNavItemExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - title: 'Create...', - icon: CreateComponentIcon, +const scaffolderNavItem = NavItemBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + title: 'Create...', + icon: CreateComponentIcon, + }, }); /** @alpha */ diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index dbb98691c9..90fc394f9d 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -38,11 +38,10 @@ import { import { createFrontendPlugin, - createApiExtension, - createPageExtension, + ApiBlueprint, createExtensionInput, - createNavItemExtension, - createSchemaFromZod, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import { @@ -62,7 +61,7 @@ import { } from '@backstage/plugin-search-react'; import { SearchResult } from '@backstage/plugin-search-common'; import { searchApiRef } from '@backstage/plugin-search-react'; -import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { rootRouteRef } from './plugin'; import { SearchClient } from './apis'; @@ -75,13 +74,15 @@ import { } from '@backstage/core-compat-api'; /** @alpha */ -export const searchApi = createApiExtension({ - factory: createApiFactory({ - api: searchApiRef, - deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, - factory: ({ discoveryApi, fetchApi }) => - new SearchClient({ discoveryApi, fetchApi }), - }), +export const searchApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: searchApiRef, + deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, + factory: ({ discoveryApi, fetchApi }) => + new SearchClient({ discoveryApi, fetchApi }), + }), + }, }); const useSearchPageStyles = makeStyles((theme: Theme) => ({ @@ -97,143 +98,152 @@ const useSearchPageStyles = makeStyles((theme: Theme) => ({ })); /** @alpha */ -export const searchPage = createPageExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - configSchema: createSchemaFromZod(z => - z.object({ - path: z.string().default('/search'), - noTrack: z.boolean().default(false), - }), - ), - inputs: { - items: createExtensionInput({ - item: createSearchResultListItemExtension.itemDataRef, - }), +export const searchPage = PageBlueprint.makeWithOverrides({ + config: { + schema: { + noTrack: z => z.boolean().default(false), + }, }, - loader: async ({ config, inputs }) => { - const getResultItemComponent = (result: SearchResult) => { - const value = inputs.items.find(item => - item?.output.item.predicate?.(result), - ); - return value?.output.item.component ?? DefaultResultListItem; - }; + inputs: { + items: createExtensionInput([SearchResultListItemBlueprint.dataRefs.item]), + }, + factory(originalFactory, { config, inputs }) { + return originalFactory({ + defaultPath: '/search', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: async () => { + const getResultItemComponent = (result: SearchResult) => { + const value = inputs.items.find(item => + item + ?.get(SearchResultListItemBlueprint.dataRefs.item) + .predicate?.(result), + ); + return ( + value?.get(SearchResultListItemBlueprint.dataRefs.item).component ?? + DefaultResultListItem + ); + }; - const Component = () => { - const classes = useSearchPageStyles(); - const { isMobile } = useSidebarPinState(); - const { types } = useSearch(); - const catalogApi = useApi(catalogApiRef); + const Component = () => { + const classes = useSearchPageStyles(); + const { isMobile } = useSidebarPinState(); + const { types } = useSearch(); + const catalogApi = useApi(catalogApiRef); - return ( - - {!isMobile &&
    } - - - - - - {!isMobile && ( - - , - }, - { - value: 'techdocs', - name: 'Documentation', - icon: , - }, - ]} - /> - - {types.includes('techdocs') && ( - { - // Return a list of entities which are documented. - const { items } = await catalogApi.getEntities({ - fields: ['metadata.name'], - filter: { - 'metadata.annotations.backstage.io/techdocs-ref': - CATALOG_FILTER_EXISTS, - }, - }); - - const names = items.map( - entity => entity.metadata.name, - ); - names.sort(); - return names; - }} + return ( + + {!isMobile &&
    } + + + + + + {!isMobile && ( + + , + }, + { + value: 'techdocs', + name: 'Documentation', + icon: , + }, + ]} /> - )} - - - - - )} - - - - {({ results }) => ( - <> - {results.map((result, index) => { - const { noTrack } = config; - const { document, ...rest } = result; - const SearchResultListItem = - getResultItemComponent(result); - return ( - - ); - })} - - )} - - - - - - - ); - }; + + {types.includes('techdocs') && ( + { + // Return a list of entities which are documented. + const { items } = await catalogApi.getEntities({ + fields: ['metadata.name'], + filter: { + 'metadata.annotations.backstage.io/techdocs-ref': + CATALOG_FILTER_EXISTS, + }, + }); - return compatWrapper( - - - - , - ); + const names = items.map( + entity => entity.metadata.name, + ); + names.sort(); + return names; + }} + /> + )} + + + + + )} + + + + {({ results }) => ( + <> + {results.map((result, index) => { + const { noTrack } = config; + const { document, ...rest } = result; + const SearchResultListItem = + getResultItemComponent(result); + return ( + + ); + })} + + )} + + + + + + + ); + }; + + return compatWrapper( + + + + , + ); + }, + }); }, }); /** @alpha */ -export const searchNavItem = createNavItemExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - title: 'Search', - icon: SearchIcon, +export const searchNavItem = NavItemBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + title: 'Search', + icon: SearchIcon, + }, }); /** @alpha */ From d2e999e4020bce0a470d872ab8f07b1a3b0ee368 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:22:06 +0200 Subject: [PATCH 293/393] chore: added kuber Signed-off-by: blam --- plugins/kubernetes/src/alpha/apis.tsx | 151 ++++++++++-------- .../kubernetes/src/alpha/entityContents.tsx | 20 +-- 2 files changed, 92 insertions(+), 79 deletions(-) diff --git a/plugins/kubernetes/src/alpha/apis.tsx b/plugins/kubernetes/src/alpha/apis.tsx index 6c880d2f53..082a2da6c7 100644 --- a/plugins/kubernetes/src/alpha/apis.tsx +++ b/plugins/kubernetes/src/alpha/apis.tsx @@ -15,7 +15,7 @@ */ import { - createApiExtension, + ApiBlueprint, createApiFactory, discoveryApiRef, fetchApiRef, @@ -40,80 +40,91 @@ import { oneloginAuthApiRef, } from '@backstage/core-plugin-api'; -export const kubernetesApiExtension = createApiExtension({ - factory: createApiFactory({ - api: kubernetesApiRef, - deps: { - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, - }, - factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => - new KubernetesBackendClient({ - discoveryApi, - fetchApi, - kubernetesAuthProvidersApi, - }), - }), +export const kubernetesApiExtension = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: kubernetesApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, + }, + factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => + new KubernetesBackendClient({ + discoveryApi, + fetchApi, + kubernetesAuthProvidersApi, + }), + }), + }, }); -export const kubernetesProxyApi = createApiExtension({ - factory: createApiFactory({ - api: kubernetesProxyApiRef, - deps: { - kubernetesApi: kubernetesApiRef, - }, - factory: ({ kubernetesApi }) => - new KubernetesProxyClient({ - kubernetesApi, - }), - }), +export const kubernetesProxyApi = ApiBlueprint.make({ + name: 'proxy', + params: { + factory: createApiFactory({ + api: kubernetesProxyApiRef, + deps: { + kubernetesApi: kubernetesApiRef, + }, + factory: ({ kubernetesApi }) => + new KubernetesProxyClient({ + kubernetesApi, + }), + }), + }, }); -export const kubernetesAuthProvidersApi = createApiExtension({ - factory: createApiFactory({ - api: kubernetesAuthProvidersApiRef, - deps: { - gitlabAuthApi: gitlabAuthApiRef, - googleAuthApi: googleAuthApiRef, - microsoftAuthApi: microsoftAuthApiRef, - oktaAuthApi: oktaAuthApiRef, - oneloginAuthApi: oneloginAuthApiRef, - }, - factory: ({ - gitlabAuthApi, - googleAuthApi, - microsoftAuthApi, - oktaAuthApi, - oneloginAuthApi, - }) => { - const oidcProviders = { - gitlab: gitlabAuthApi, - google: googleAuthApi, - microsoft: microsoftAuthApi, - okta: oktaAuthApi, - onelogin: oneloginAuthApi, - }; - - return new KubernetesAuthProviders({ - microsoftAuthApi, +export const kubernetesAuthProvidersApi = ApiBlueprint.make({ + name: 'auth-providers', + params: { + factory: createApiFactory({ + api: kubernetesAuthProvidersApiRef, + deps: { + gitlabAuthApi: gitlabAuthApiRef, + googleAuthApi: googleAuthApiRef, + microsoftAuthApi: microsoftAuthApiRef, + oktaAuthApi: oktaAuthApiRef, + oneloginAuthApi: oneloginAuthApiRef, + }, + factory: ({ + gitlabAuthApi, googleAuthApi, - oidcProviders, - }); - }, - }), + microsoftAuthApi, + oktaAuthApi, + oneloginAuthApi, + }) => { + const oidcProviders = { + gitlab: gitlabAuthApi, + google: googleAuthApi, + microsoft: microsoftAuthApi, + okta: oktaAuthApi, + onelogin: oneloginAuthApi, + }; + + return new KubernetesAuthProviders({ + microsoftAuthApi, + googleAuthApi, + oidcProviders, + }); + }, + }), + }, }); -export const kubernetesClusterLinkFormatterApi = createApiExtension({ - factory: createApiFactory({ - api: kubernetesClusterLinkFormatterApiRef, - deps: { googleAuthApi: googleAuthApiRef }, - factory: deps => { - const formatters = getDefaultFormatters(deps); - return new KubernetesClusterLinkFormatter({ - formatters, - defaultFormatterName: DEFAULT_FORMATTER_NAME, - }); - }, - }), +export const kubernetesClusterLinkFormatterApi = ApiBlueprint.make({ + name: 'cluster-link-formatter', + params: { + factory: createApiFactory({ + api: kubernetesClusterLinkFormatterApiRef, + deps: { googleAuthApi: googleAuthApiRef }, + factory: deps => { + const formatters = getDefaultFormatters(deps); + return new KubernetesClusterLinkFormatter({ + formatters, + defaultFormatterName: DEFAULT_FORMATTER_NAME, + }); + }, + }), + }, }); diff --git a/plugins/kubernetes/src/alpha/entityContents.tsx b/plugins/kubernetes/src/alpha/entityContents.tsx index 9ad0b6843f..de11310981 100644 --- a/plugins/kubernetes/src/alpha/entityContents.tsx +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -19,16 +19,18 @@ import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; import { rootCatalogKubernetesRouteRef } from '../plugin'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; -export const entityKubernetesContent = createEntityContentExtension({ - defaultPath: 'kubernetes', - defaultTitle: 'Kubernetes', +export const entityKubernetesContent = EntityContentBlueprint.make({ name: 'kubernetes', - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), - loader: () => - import('./KubernetesContentPage').then(m => - compatWrapper(), - ), + params: { + defaultPath: 'kubernetes', + defaultTitle: 'Kubernetes', + routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), + loader: () => + import('./KubernetesContentPage').then(m => + compatWrapper(), + ), + }, }); From 75e79518eafc6e6eb55585f166667418419662de Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:22:16 +0200 Subject: [PATCH 294/393] chore: some more migrations Signed-off-by: blam --- plugins/catalog-import/src/alpha.tsx | 68 ++++++----- plugins/devtools/src/alpha/plugin.tsx | 54 ++++---- plugins/org/src/alpha.tsx | 58 +++++---- plugins/techdocs/src/alpha.tsx | 170 ++++++++++++++------------ plugins/user-settings/src/alpha.tsx | 52 ++++---- 5 files changed, 221 insertions(+), 181 deletions(-) diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 9f685cf778..cf63c92338 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -25,9 +25,9 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { - createApiExtension, - createPageExtension, createFrontendPlugin, + PageBlueprint, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; import { scmAuthApiRef, @@ -40,43 +40,47 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; // TODO: It's currently possible to override the import page with a custom one. We need to decide // whether this type of override is typically done with an input or by overriding the entire extension. -const catalogImportPage = createPageExtension({ - defaultPath: '/catalog-import', - routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('./components/ImportPage').then(m => - compatWrapper(), - ), +const catalogImportPage = PageBlueprint.make({ + params: { + defaultPath: '/catalog-import', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('./components/ImportPage').then(m => + compatWrapper(), + ), + }, }); -const catalogImportApi = createApiExtension({ - factory: createApiFactory({ - api: catalogImportApiRef, - deps: { - discoveryApi: discoveryApiRef, - scmAuthApi: scmAuthApiRef, - fetchApi: fetchApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - catalogApi: catalogApiRef, - configApi: configApiRef, - }, - factory: ({ - discoveryApi, - scmAuthApi, - fetchApi, - scmIntegrationsApi, - catalogApi, - configApi, - }) => - new CatalogImportClient({ +const catalogImportApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: catalogImportApiRef, + deps: { + discoveryApi: discoveryApiRef, + scmAuthApi: scmAuthApiRef, + fetchApi: fetchApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + catalogApi: catalogApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, scmAuthApi, - scmIntegrationsApi, fetchApi, + scmIntegrationsApi, catalogApi, configApi, - }), - }), + }) => + new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + fetchApi, + catalogApi, + configApi, + }), + }), + }, }); /** @alpha */ diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 73b94f52f8..4c62dd3155 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -16,13 +16,13 @@ import React from 'react'; import { - createApiExtension, createApiFactory, - createNavItemExtension, - createPageExtension, createFrontendPlugin, discoveryApiRef, fetchApiRef, + ApiBlueprint, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import { devToolsApiRef, DevToolsClient } from '../api'; @@ -34,33 +34,39 @@ import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; /** @alpha */ -export const devToolsApi = createApiExtension({ - factory: createApiFactory({ - api: devToolsApiRef, - deps: { - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ discoveryApi, fetchApi }) => - new DevToolsClient({ discoveryApi, fetchApi }), - }), +export const devToolsApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: devToolsApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new DevToolsClient({ discoveryApi, fetchApi }), + }), + }, }); /** @alpha */ -export const devToolsPage = createPageExtension({ - defaultPath: '/devtools', - routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('../components/DevToolsPage').then(m => - compatWrapper(), - ), +export const devToolsPage = PageBlueprint.make({ + params: { + defaultPath: '/devtools', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('../components/DevToolsPage').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -export const devToolsNavItem = createNavItemExtension({ - title: 'DevTools', - routeRef: convertLegacyRouteRef(rootRouteRef), - icon: BuildIcon, +export const devToolsNavItem = NavItemBlueprint.make({ + params: { + title: 'DevTools', + routeRef: convertLegacyRouteRef(rootRouteRef), + icon: BuildIcon, + }, }); /** @alpha */ diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index 481d9f2b63..61054a2d8a 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -21,46 +21,54 @@ import { import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import React from 'react'; import { catalogIndexRouteRef } from './routes'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; /** @alpha */ -const EntityGroupProfileCard = createEntityCardExtension({ +const EntityGroupProfileCard = EntityCardBlueprint.make({ name: 'group-profile', - filter: 'kind:group', - loader: async () => - import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:group', + loader: async () => + import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -const EntityMembersListCard = createEntityCardExtension({ +const EntityMembersListCard = EntityCardBlueprint.make({ name: 'members-list', - filter: 'kind:group', - loader: async () => - import('./components/Cards/Group/MembersList/MembersListCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:group', + loader: async () => + import('./components/Cards/Group/MembersList/MembersListCard').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -const EntityOwnershipCard = createEntityCardExtension({ +const EntityOwnershipCard = EntityCardBlueprint.make({ name: 'ownership', - filter: 'kind:group,user', - loader: async () => - import('./components/Cards/OwnershipCard/OwnershipCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:group,user', + loader: async () => + import('./components/Cards/OwnershipCard/OwnershipCard').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -const EntityUserProfileCard = createEntityCardExtension({ +const EntityUserProfileCard = EntityCardBlueprint.make({ name: 'user-profile', - filter: 'kind:user', - loader: async () => - import('./components/Cards/User/UserProfileCard/UserProfileCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:user', + loader: async () => + import('./components/Cards/User/UserProfileCard/UserProfileCard').then( + m => compatWrapper(), + ), + }, }); /** @alpha */ diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index d1a186f227..b0d0d3d467 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -18,12 +18,11 @@ import React from 'react'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { createFrontendPlugin, - createSchemaFromZod, - createApiExtension, - createPageExtension, - createNavItemExtension, + ApiBlueprint, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; -import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { configApiRef, createApiFactory, @@ -45,64 +44,73 @@ import { rootDocsRouteRef, rootRouteRef, } from './routes'; -import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; /** @alpha */ -const techDocsStorageApi = createApiExtension({ - factory: createApiFactory({ - api: techdocsStorageApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ configApi, discoveryApi, fetchApi }) => - new TechDocsStorageClient({ - configApi, - discoveryApi, - fetchApi, - }), - }), +const techDocsStorageApi = ApiBlueprint.make({ + name: 'storage', + params: { + factory: createApiFactory({ + api: techdocsStorageApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ configApi, discoveryApi, fetchApi }) => + new TechDocsStorageClient({ + configApi, + discoveryApi, + fetchApi, + }), + }), + }, }); /** @alpha */ -const techDocsClientApi = createApiExtension({ - factory: createApiFactory({ - api: techdocsApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ configApi, discoveryApi, fetchApi }) => - new TechDocsClient({ - configApi, - discoveryApi, - fetchApi, - }), - }), +const techDocsClientApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: techdocsApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ configApi, discoveryApi, fetchApi }) => + new TechDocsClient({ + configApi, + discoveryApi, + fetchApi, + }), + }), + }, }); /** @alpha */ export const techDocsSearchResultListItemExtension = - createSearchResultListItemExtension({ - configSchema: createSchemaFromZod(z => - z.object({ - // TODO: Define how the icon can be configurable - title: z.string().optional(), - lineClamp: z.number().default(5), - asLink: z.boolean().default(true), - asListItem: z.boolean().default(true), - noTrack: z.boolean().default(false), - }), - ), - predicate: result => result.type === 'techdocs', - component: async ({ config }) => { - const { TechDocsSearchResultListItem } = await import( - './search/components/TechDocsSearchResultListItem' - ); - return props => - compatWrapper(); + SearchResultListItemBlueprint.makeWithOverrides({ + config: { + schema: { + title: z => z.string().optional(), + lineClamp: z => z.number().default(5), + asLink: z => z.boolean().default(true), + asListItem: z => z.boolean().default(true), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + predicate: result => result.type === 'techdocs', + component: async () => { + const { TechDocsSearchResultListItem } = await import( + './search/components/TechDocsSearchResultListItem' + ); + return props => + compatWrapper( + , + ); + }, + }); }, }); @@ -111,13 +119,15 @@ export const techDocsSearchResultListItemExtension = * * @alpha */ -const techDocsPage = createPageExtension({ - defaultPath: '/docs', - routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('./home/components/TechDocsIndexPage').then(m => - compatWrapper(), - ), +const techDocsPage = PageBlueprint.make({ + params: { + defaultPath: '/docs', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('./home/components/TechDocsIndexPage').then(m => + compatWrapper(), + ), + }, }); /** @@ -125,14 +135,16 @@ const techDocsPage = createPageExtension({ * * @alpha */ -const techDocsReaderPage = createPageExtension({ +const techDocsReaderPage = PageBlueprint.make({ name: 'reader', - defaultPath: '/docs/:namespace/:kind/:name', - routeRef: convertLegacyRouteRef(rootDocsRouteRef), - loader: () => - import('./reader/components/TechDocsReaderPage').then(m => - compatWrapper(), - ), + params: { + defaultPath: '/docs/:namespace/:kind/:name', + routeRef: convertLegacyRouteRef(rootDocsRouteRef), + loader: () => + import('./reader/components/TechDocsReaderPage').then(m => + compatWrapper(), + ), + }, }); /** @@ -140,18 +152,22 @@ const techDocsReaderPage = createPageExtension({ * * @alpha */ -const techDocsEntityContent = createEntityContentExtension({ - defaultPath: 'docs', - defaultTitle: 'TechDocs', - loader: () => - import('./Router').then(m => compatWrapper()), +const techDocsEntityContent = EntityContentBlueprint.make({ + params: { + defaultPath: 'docs', + defaultTitle: 'TechDocs', + loader: () => + import('./Router').then(m => compatWrapper()), + }, }); /** @alpha */ -const techDocsNavItem = createNavItemExtension({ - icon: LibraryBooks, - title: 'Docs', - routeRef: convertLegacyRouteRef(rootRouteRef), +const techDocsNavItem = NavItemBlueprint.make({ + params: { + icon: LibraryBooks, + title: 'Docs', + routeRef: convertLegacyRouteRef(rootRouteRef), + }, }); /** @alpha */ diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index cfc3369487..bdd16acf0f 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -16,9 +16,9 @@ import { coreExtensionData, createExtensionInput, - createNavItemExtension, - createPageExtension, createFrontendPlugin, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import { convertLegacyRouteRef, @@ -32,32 +32,38 @@ import React from 'react'; export * from './translation'; -const userSettingsPage = createPageExtension({ - defaultPath: '/settings', - routeRef: convertLegacyRouteRef(settingsRouteRef), +const userSettingsPage = PageBlueprint.makeWithOverrides({ inputs: { - providerSettings: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true, optional: true }, - ), + providerSettings: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), + }, + factory(originalFactory, { inputs }) { + return originalFactory({ + defaultPath: '/settings', + routeRef: convertLegacyRouteRef(settingsRouteRef), + loader: () => + import('./components/SettingsPage').then(m => + compatWrapper( + , + ), + ), + }); }, - loader: ({ inputs }) => - import('./components/SettingsPage').then(m => - compatWrapper( - , - ), - ), }); /** @alpha */ -export const settingsNavItem = createNavItemExtension({ - routeRef: convertLegacyRouteRef(settingsRouteRef), - title: 'Settings', - icon: SettingsIcon, +export const settingsNavItem = NavItemBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(settingsRouteRef), + title: 'Settings', + icon: SettingsIcon, + }, }); /** From 4a8b94ee93a8a83726c28a7bdce1fe69ab758fa3 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:28:34 +0200 Subject: [PATCH 295/393] chore: last one Signed-off-by: blam Signed-off-by: blam --- .../AppVisualizerPage/DetailedVisualizer.tsx | 18 ++++++------ plugins/app-visualizer/src/plugin.tsx | 28 +++++++++++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index b2c9685bab..d22c2d94db 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -20,9 +20,9 @@ import { ExtensionDataRef, RouteRef, coreExtensionData, - createApiExtension, - createNavItemExtension, - createThemeExtension, + ApiBlueprint, + NavItemBlueprint, + ThemeBlueprint, useRouteRef, } from '@backstage/frontend-plugin-api'; import Box from '@material-ui/core/Box'; @@ -66,9 +66,9 @@ const getOutputColor = createOutputColorGenerator( [coreExtensionData.reactElement.id]: colors.green[500], [coreExtensionData.routePath.id]: colors.yellow[500], [coreExtensionData.routeRef.id]: colors.purple[500], - [createApiExtension.factoryDataRef.id]: colors.blue[500], - [createThemeExtension.themeDataRef.id]: colors.lime[500], - [createNavItemExtension.targetDataRef.id]: colors.orange[500], + [ApiBlueprint.dataRefs.factory.id]: colors.blue[500], + [ThemeBlueprint.dataRefs.theme.id]: colors.lime[500], + [NavItemBlueprint.dataRefs.target.id]: colors.orange[500], }, [ @@ -323,11 +323,11 @@ function Extension(props: { node: AppNode; depth: number }) { const legendMap = { 'React Element': coreExtensionData.reactElement, - 'Utility API': createApiExtension.factoryDataRef, + 'Utility API': ApiBlueprint.dataRefs.factory, 'Route Path': coreExtensionData.routePath, 'Route Ref': coreExtensionData.routeRef, - 'Nav Target': createNavItemExtension.targetDataRef, - Theme: createThemeExtension.themeDataRef, + 'Nav Target': NavItemBlueprint.dataRefs.target, + Theme: ThemeBlueprint.dataRefs.theme, }; function Legend() { diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index b41f1c69e8..0e18b692e8 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -15,27 +15,33 @@ */ import { - createNavItemExtension, - createPageExtension, createFrontendPlugin, createRouteRef, + NavItemBlueprint, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import VisualizerIcon from '@material-ui/icons/Visibility'; import React from 'react'; const rootRouteRef = createRouteRef(); -const appVisualizerPage = createPageExtension({ - defaultPath: '/visualizer', - routeRef: rootRouteRef, - loader: () => - import('./components/AppVisualizerPage').then(m => ), +const appVisualizerPage = PageBlueprint.make({ + params: { + defaultPath: '/visualizer', + routeRef: rootRouteRef, + loader: () => + import('./components/AppVisualizerPage').then(m => ( + + )), + }, }); -export const appVisualizerNavItem = createNavItemExtension({ - title: 'Visualizer', - icon: VisualizerIcon, - routeRef: rootRouteRef, +export const appVisualizerNavItem = NavItemBlueprint.make({ + params: { + title: 'Visualizer', + icon: VisualizerIcon, + routeRef: rootRouteRef, + }, }); /** @public */ From af7be32e90e6a3d159bf58a4b70190c57d72b604 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 16:32:09 +0200 Subject: [PATCH 296/393] chore: api-reports Signed-off-by: blam --- plugins/app-visualizer/api-report.md | 55 ++++- plugins/catalog-graph/api-report-alpha.md | 135 ++++++++++- plugins/catalog-graph/src/alpha.tsx | 2 +- plugins/catalog-import/api-report-alpha.md | 42 +++- plugins/catalog/api-report-alpha.md | 261 +++++++++++++++++++++ plugins/catalog/src/alpha/apis.tsx | 5 +- plugins/catalog/src/alpha/filters.tsx | 1 - plugins/devtools/api-report-alpha.md | 60 ++++- plugins/kubernetes/api-report-alpha.md | 69 ++++++ plugins/org/api-report-alpha.md | 131 ++++++++++- plugins/scaffolder/api-report-alpha.md | 59 ++++- plugins/search/api-report-alpha.md | 159 +++++++++++-- plugins/techdocs/api-report-alpha.md | 187 ++++++++++++++- plugins/user-settings/api-report-alpha.md | 92 +++++++- 14 files changed, 1195 insertions(+), 63 deletions(-) diff --git a/plugins/app-visualizer/api-report.md b/plugins/app-visualizer/api-report.md index fd3749b159..cab8b113b6 100644 --- a/plugins/app-visualizer/api-report.md +++ b/plugins/app-visualizer/api-report.md @@ -3,10 +3,63 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const visualizerPlugin: BackstagePlugin<{}, {}, {}>; +const visualizerPlugin: BackstagePlugin< + {}, + {}, + { + 'page:app-visualizer': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + undefined + >; + 'nav-item:app-visualizer': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + } +>; export default visualizerPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index e651946463..03ee352227 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -3,8 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Direction } from '@backstage/plugin-catalog-graph'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) @@ -19,7 +27,132 @@ const _default: BackstagePlugin< namespace: string; }>; }, - {} + { + 'entity-card:catalog-graph/relations': ExtensionDefinition< + { + kinds: string[] | undefined; + relations: string[] | undefined; + maxDepth: number | undefined; + unidirectional: boolean | undefined; + mergeRelations: boolean | undefined; + direction: Direction | undefined; + relationPairs: [string, string][] | undefined; + zoom: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + curve: 'curveStepBefore' | 'curveMonotoneX' | undefined; + title: string | undefined; + height: number | undefined; + } & { + filter: string | undefined; + }, + { + height?: number | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; + direction?: Direction | undefined; + title?: string | undefined; + zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + relations?: string[] | undefined; + maxDepth?: number | undefined; + kinds?: string[] | undefined; + unidirectional?: boolean | undefined; + mergeRelations?: boolean | undefined; + relationPairs?: [string, string][] | undefined; + } & { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'entity-card', + undefined, + 'relations' + >; + 'page:catalog-graph': ExtensionDefinition< + { + selectedKinds: string[] | undefined; + selectedRelations: string[] | undefined; + rootEntityRefs: string[] | undefined; + maxDepth: number | undefined; + unidirectional: boolean | undefined; + mergeRelations: boolean | undefined; + direction: Direction | undefined; + showFilters: boolean | undefined; + curve: 'curveStepBefore' | 'curveMonotoneX' | undefined; + kinds: string[] | undefined; + relations: string[] | undefined; + relationPairs: [string, string][] | undefined; + zoom: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + } & { + path: string | undefined; + }, + { + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; + direction?: Direction | undefined; + zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + relations?: string[] | undefined; + rootEntityRefs?: string[] | undefined; + maxDepth?: number | undefined; + kinds?: string[] | undefined; + unidirectional?: boolean | undefined; + mergeRelations?: boolean | undefined; + relationPairs?: [string, string][] | undefined; + selectedRelations?: string[] | undefined; + selectedKinds?: string[] | undefined; + showFilters?: boolean | undefined; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'page', + undefined, + undefined + >; + } >; export default _default; diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index 11c845814f..8186fd3b96 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -25,7 +25,7 @@ import { } from '@backstage/core-compat-api'; import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; -import { Direction } from './components'; +import { Direction } from '@backstage/plugin-catalog-graph'; const CatalogGraphEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'relations', diff --git a/plugins/catalog-import/api-report-alpha.md b/plugins/catalog-import/api-report-alpha.md index 51e3d99fd7..3129d13e07 100644 --- a/plugins/catalog-import/api-report-alpha.md +++ b/plugins/catalog-import/api-report-alpha.md @@ -3,7 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) @@ -12,7 +17,42 @@ const _default: BackstagePlugin< importPage: RouteRef; }, {}, - {} + { + 'api:catalog-import': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + undefined + >; + 'page:catalog-import': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + undefined + >; + } >; export default _default; diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 399580d6a2..386024e0c0 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -5,6 +5,8 @@ ```ts /// +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; @@ -14,10 +16,13 @@ import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; +import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha @@ -164,6 +169,41 @@ const _default: BackstagePlugin< unregisterRedirect: ExternalRouteRef; }, { + 'nav-item:catalog': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + 'api:catalog/starred-entities': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + 'starred-entities' + >; + 'api:catalog/entity-presentation': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + 'entity-presentation' + >; 'entity-card:catalog/about': ExtensionDefinition< { filter: string | undefined; @@ -500,6 +540,227 @@ const _default: BackstagePlugin< undefined, 'overview' >; + 'catalog-filter:catalog/tag': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'catalog-filter', + undefined, + 'tag' + >; + 'catalog-filter:catalog/kind': ExtensionDefinition< + { + initialFilter: string; + }, + { + initialFilter?: string | undefined; + }, + ConfigurableExtensionDataRef, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'catalog-filter', + undefined, + 'kind' + >; + 'catalog-filter:catalog/type': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'catalog-filter', + undefined, + 'type' + >; + 'catalog-filter:catalog/mode': ExtensionDefinition< + { + mode: 'all' | 'owners-only' | undefined; + }, + { + mode?: 'all' | 'owners-only' | undefined; + }, + ConfigurableExtensionDataRef, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'catalog-filter', + undefined, + 'mode' + >; + 'catalog-filter:catalog/namespace': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'catalog-filter', + undefined, + 'namespace' + >; + 'catalog-filter:catalog/lifecycle': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'catalog-filter', + undefined, + 'lifecycle' + >; + 'catalog-filter:catalog/processing-status': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'catalog-filter', + undefined, + 'processing-status' + >; + 'catalog-filter:catalog/list': ExtensionDefinition< + { + initialFilter: 'all' | 'owned' | 'starred'; + }, + { + initialFilter?: 'all' | 'owned' | 'starred' | undefined; + }, + ConfigurableExtensionDataRef, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'catalog-filter', + undefined, + 'list' + >; + 'page:catalog': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + filters: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: false; + } + >; + }, + 'page', + undefined, + undefined + >; + 'page:catalog/entity': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + contents: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-content-title', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + } + >; + }, + 'page', + undefined, + 'entity' + >; + 'search-result-list-item:catalog': ExtensionDefinition< + { + noTrack: boolean; + }, + { + noTrack?: boolean | undefined; + }, + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + {}, + 'search-result-list-item', + undefined, + undefined + >; } >; export default _default; diff --git a/plugins/catalog/src/alpha/apis.tsx b/plugins/catalog/src/alpha/apis.tsx index 937ba118b3..d0f80c3aec 100644 --- a/plugins/catalog/src/alpha/apis.tsx +++ b/plugins/catalog/src/alpha/apis.tsx @@ -21,10 +21,7 @@ import { storageApiRef, } from '@backstage/core-plugin-api'; import { CatalogClient } from '@backstage/catalog-client'; -import { - ApiBlueprint, - createApiExtension, -} from '@backstage/frontend-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; import { catalogApiRef, entityPresentationApiRef, diff --git a/plugins/catalog/src/alpha/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index f4d58e8273..3507658731 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -15,7 +15,6 @@ */ import React from 'react'; -import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; import { CatalogFilterBlueprint } from './blueprints'; const catalogTagCatalogFilter = CatalogFilterBlueprint.make({ diff --git a/plugins/devtools/api-report-alpha.md b/plugins/devtools/api-report-alpha.md index 23ede8144c..f5ca1f98c1 100644 --- a/plugins/devtools/api-report-alpha.md +++ b/plugins/devtools/api-report-alpha.md @@ -3,7 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) @@ -12,7 +18,59 @@ const _default: BackstagePlugin< root: RouteRef; }, {}, - {} + { + 'api:devtools': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + undefined + >; + 'page:devtools': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + undefined + >; + 'nav-item:devtools': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + } >; export default _default; diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md index f3260208c3..6b53443e2a 100644 --- a/plugins/kubernetes/api-report-alpha.md +++ b/plugins/kubernetes/api-report-alpha.md @@ -5,9 +5,11 @@ ```ts /// +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -40,6 +42,73 @@ const _default: BackstagePlugin< undefined, undefined >; + 'entity-content:kubernetes/kubernetes': ExtensionDefinition< + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-content', + undefined, + 'kubernetes' + >; + 'api:kubernetes/proxy': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + 'proxy' + >; + 'api:kubernetes/auth-providers': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + 'auth-providers' + >; + 'api:kubernetes/cluster-link-formatter': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + 'cluster-link-formatter' + >; } >; export default _default; diff --git a/plugins/org/api-report-alpha.md b/plugins/org/api-report-alpha.md index be74cde141..d96825c601 100644 --- a/plugins/org/api-report-alpha.md +++ b/plugins/org/api-report-alpha.md @@ -4,7 +4,11 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -12,7 +16,132 @@ const _default: BackstagePlugin< { catalogIndex: ExternalRouteRef; }, - {} + { + 'entity-card:org/group-profile': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'group-profile' + >; + 'entity-card:org/members-list': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'members-list' + >; + 'entity-card:org/ownership': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'ownership' + >; + 'entity-card:org/user-profile': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-card', + undefined, + 'user-profile' + >; + } >; export default _default; diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index de0fc463a9..4025174c55 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -3,11 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; +import { IconComponent } from '@backstage/core-plugin-api'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; import { PathParams } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; @@ -37,7 +42,59 @@ const _default: BackstagePlugin< namespace: string; }>; }, - {} + { + 'api:scaffolder': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + undefined + >; + 'page:scaffolder': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + undefined + >; + 'nav-item:scaffolder': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + } >; export default _default; diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index e6f91d6d19..8a47cb41fc 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -3,10 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; +import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -14,7 +21,78 @@ const _default: BackstagePlugin< root: RouteRef; }, {}, - {} + { + 'api:search': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + undefined + >; + 'nav-item:search': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + 'page:search': ExtensionDefinition< + { + noTrack: boolean; + } & { + path: string | undefined; + }, + { + noTrack?: boolean | undefined; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + items: ExtensionInput< + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + { + singleton: false; + optional: false; + } + >; + }, + 'page', + undefined, + undefined + >; + } >; export default _default; @@ -22,43 +100,72 @@ export default _default; export const searchApi: ExtensionDefinition< {}, {}, - never, - never, - string | undefined, - string | undefined, - string | undefined + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + undefined >; // @alpha (undocumented) export const searchNavItem: ExtensionDefinition< - { - title: string; - }, - { - title?: string | undefined; - }, - never, - never, - string | undefined, - string | undefined, - string | undefined + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined >; // @alpha (undocumented) export const searchPage: ExtensionDefinition< { - path: string; noTrack: boolean; + } & { + path: string | undefined; }, { - path: string; - noTrack: boolean; + noTrack?: boolean | undefined; + } & { + path?: string | undefined; }, - AnyExtensionDataRef, - {}, - string | undefined, - string | undefined, - string | undefined + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + items: ExtensionInput< + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + { + singleton: false; + optional: false; + } + >; + }, + 'page', + undefined, + undefined >; // (No @packageDocumentation comment for this package) diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index f7c6679139..14a2400534 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -3,9 +3,19 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; +import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -19,31 +29,182 @@ const _default: BackstagePlugin< entityContent: RouteRef; }, {}, - {} + { + 'nav-item:techdocs': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + 'api:techdocs/storage': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + 'api', + undefined, + 'storage' + >; + 'search-result-list-item:techdocs': ExtensionDefinition< + { + title: string | undefined; + lineClamp: number; + asLink: boolean; + asListItem: boolean; + } & { + noTrack: boolean; + }, + { + title?: string | undefined; + lineClamp?: number | undefined; + asListItem?: boolean | undefined; + asLink?: boolean | undefined; + } & { + noTrack?: boolean | undefined; + }, + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'search-result-list-item', + undefined, + undefined + >; + 'page:techdocs/reader': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + 'page', + undefined, + 'reader' + >; + 'entity-content:techdocs': ExtensionDefinition< + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + 'entity-content', + undefined, + undefined + >; + } >; export default _default; // @alpha (undocumented) export const techDocsSearchResultListItemExtension: ExtensionDefinition< { + title: string | undefined; lineClamp: number; - noTrack: boolean; - asListItem: boolean; asLink: boolean; - title?: string | undefined; + asListItem: boolean; + } & { + noTrack: boolean; }, { - lineClamp: number; - noTrack: boolean; - asListItem: boolean; - asLink: boolean; title?: string | undefined; + lineClamp?: number | undefined; + asListItem?: boolean | undefined; + asLink?: boolean | undefined; + } & { + noTrack?: boolean | undefined; }, - never, - never, - string | undefined, - string | undefined, - string | undefined + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + 'search-result-list-item', + undefined, + undefined >; // (No @packageDocumentation comment for this package) diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index 700e06a7ad..b79657cabc 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -3,8 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -14,23 +19,86 @@ const _default: BackstagePlugin< root: RouteRef; }, {}, - {} + { + 'nav-item:user-settings': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined + >; + 'page:user-settings': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + providerSettings: ExtensionInput< + ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + >, + { + singleton: true; + optional: true; + } + >; + }, + 'page', + undefined, + undefined + >; + } >; export default _default; // @alpha (undocumented) export const settingsNavItem: ExtensionDefinition< - { - title: string; - }, - { - title?: string | undefined; - }, - never, - never, - string | undefined, - string | undefined, - string | undefined + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + 'nav-item', + undefined, + undefined >; // @alpha (undocumented) From c7603e839d28c14ca6bf62778ad76bcba0955fdf Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 16:38:27 +0200 Subject: [PATCH 297/393] chore: added changeset Signed-off-by: blam --- .changeset/giant-buttons-guess.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .changeset/giant-buttons-guess.md diff --git a/.changeset/giant-buttons-guess.md b/.changeset/giant-buttons-guess.md new file mode 100644 index 0000000000..7a7842259e --- /dev/null +++ b/.changeset/giant-buttons-guess.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-app-visualizer': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-org': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-search': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-user-settings': patch +--- + +Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead From c7743442caa09d6b68bff9d25e334cdb941758cc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 16:45:12 +0200 Subject: [PATCH 298/393] docs/frontend-system: tweak config override + input override docs Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 111 ++++++++++++++++-- 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 5e55ef3d0e..3ce74139fa 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -141,7 +141,34 @@ const myOverrideExtension = myExtension.override({ Overriding the configuration schema works very similar to overriding the declared inputs. You can define new configuration fields that will be merged with the existing ones, but you can not re-declare existing fields. The following example shows how to override an extension and add a new configuration field: ```tsx -// Original extension +const exampleExtension = createExtension({ + config: { + schema: { + foo: z => z.string(), + }, + }, + // ... +}); + +const overrideExtension = exampleExtension.override({ + config: { + schema: { + bar: z => z.string(), + }, + }, + factory(originalFactory, { config }) { + // + console.log(`foo=${config.foo} bar=${config.bar}`); + return originalFactory(); + }, +}); +``` + +## Overriding original factory config context + +In all examples so far we have called the `originalFactory` callback without any arguments. It is however possible to override parts of the factory context for the original factory using the first parameter of the original factory. This can be useful if you want to override the provided configuration or change the inputs in some way. Note that if you are implementing a `factory` for a blueprint, the override factory context will instead be the second parameter of the original factory function. The following is an example of how to override the configuration for the original factory: + +```tsx const exampleExtension = createExtension({ name: 'example', config: { @@ -151,21 +178,83 @@ const exampleExtension = createExtension({ }, output: [coreExtensionData.reactElement], factory: ({ config }) => [ - coreExtensionData.reactElement(), + coreExtensionData.reactElement( + , + ), ], }); const overrideExtension = exampleExtension.override({ - config: { - schema: { - additionalField: z => z.string().optional(), - }, - }, factory(originalFactory, { config }) { - console.log( - `additionalField=${config.additionalField} layout=${config.layout}`, - ); - return originalFactory(); + return originalFactory({ + config: { + // Switch default layout from 'list' to 'grid' + layout: config.layout ?? 'grid', + }, + }); + }, +}); +``` + +As can be seen in the above example we can provide a new configuration object in the `originalFactory` call using the `config` property. When providing the `config` property we will completely override the original configuration object that would otherwise have been provided to the original factory. Note that this object must adhere to the output type of the configuration schema, which might not be intuitive. It's due to the configuration having already been processed and validated by Zod at this point, which means that things like defaults in the schema will not be applied again. + +## Overriding original factory inputs context + +In addition to the configuration, you are also able to override the inputs provided to the original factory. Just like when overriding configuration you will completely replace the original inputs with the new ones, but you are able to forward the inputs that you are receiving to the override factory. + +You can override each input in one of two ways, which can not be combined. You can forward (or not forward) the original input, optionally filtering out individual items or reordering them. Or you can provide new values for the input, which will replace the original input. When providing new values you must forward all existing inputs and the inputs can not be reordered, and when forwarding the existing inputs you can not provide new values. + +The following example shows how to override the values provided for each input item: + +```tsx +const exampleExtension = createExtension({ + inputs: { + items: createExtensionInput([coreExtensionData.reactElement]), + }, + // ... +}); + +const overrideExtension = exampleExtension.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + items: inputs.items.map(i => [ + coreExtensionData.reactElement( + {i.get(coreExtensionData.reactElement)}, + ), + ]), + }, + }); + }, +}); +``` + +In contrast, the following example shows how to forward the original inputs, but in a different order: + +```tsx +const exampleExtension = createExtension({ + inputs: { + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), + items: createExtensionInput([coreExtensionData.reactElement]), + }, + // ... +}); + +const overrideExtension = exampleExtension.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + // We can also skip forwarding the original input, if we want to remove it + content: inputs.content, + // Sort items input by their extension ID + items: inputs.items.toSorted((a, b) => + a.node.spec.id.localCompare(b.node.spec.id), + ), + }, + }); }, }); ``` From 177e362d330cbd944266a745b522026d53bcba93 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 16:59:11 +0200 Subject: [PATCH 299/393] docs/frontend-system: slim down createExtensionOverrides docs Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 144 +++--------------- 1 file changed, 18 insertions(+), 126 deletions(-) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 3ce74139fa..aa83c32e8b 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -259,157 +259,49 @@ const overrideExtension = exampleExtension.override({ }); ``` -## Override App Extensions +## Installing override extension in an app -In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. +To install extension overrides in a Backstage app you should use `plugin.withOverrides` whenever you are overriding or adding extensions to for a plugin. See the section on [overriding a plugin](./15-plugins.md#overriding-a-plugin) for more information. -### Example +There is also a `createExtensionOverrides` function that can be used to install a collection of standalone extensions in an app. This method will be replaced with a different mechanism in the future, but for now remains the only way to override the built-in extensions in the app or to package extensions for a plugin package separate from the plugin itself. -In the example below, we create a file that exports custom extensions for the app's `light` and `dark` themes: +Note that while using either of these options you don't necessarily need to use the extension `.override(...)` method to create the overrides. You can also create new extensions with `createExtension` or a blueprint that are either completely net-new extensions, or override an existing extension by using the same `kind`, `namespace` and `name` to produce the same extension ID. -```tsx title="packages/app/src/themes.tsx" -import { - createThemeExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; -import { apertureThemes } from './themes'; -import { ApertureLightIcon, ApertureDarkIcon } from './icons'; +### Creating a standalone extension bundle -// Creating a light theme extension -const apertureLightTheme = createThemeExtension({ - // highlight-start - namespace: 'app', - name: 'light', - // highlight-end - title: 'Aperture Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), -}); +The following example shows how to create a standalone extension bundle that overrides the search page from the search plugin: -// Creating a dark theme extension -const apertureDarkTheme = createThemeExtension({ - // highlight-start - namespace: 'app', - name: 'dark', - // highlight-end - title: 'Aperture Dark Theme', - variant: 'dark', - icon: , - Provider: ({ children }) => ( - - ), -}); - -// Creating an extension overrides preset -export default createExtensionOverrides({ - extensions: [apertureLightTheme, apertureDarkTheme], -}); -``` - -Note that we declare `namespace` as `'app'` while creating the themes, so the system knows we are overriding app extensions. Additionally, to specifically override the `light` and `dark` theme extensions, we set the `name` option to `light` and `dark`. Therefore, to override app theme extensions, we ensure that the extension `namespace` and `name` match those of the default app theme extension definitions. - -Now we are able to use the overrides in a Backstage app: - -```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; -import themeOverrides from './themes'; - -const app = createApp({ - // highlight-next-line - features: [themeOverrides], -}); - -export default app.createRoot(). -``` - -If the plugin you want to change is internal to your company or you just want to replace one of the application's core extensions, you can decide to store the overrides code directly in the app package or extract them to a separate package. - -Note that it can still be a good idea to split your overrides out into separate packages in large projects. But it's up to you to decide how to group the extensions into extension overrides. - -## Override Plugin Extensions - -To override an extension that is provided by a plugin, you need to provide a new extension that has the same ID as the existing extension. That is, all kind, namespace, and name options must match the extension you want to replace. This means that you typically need to provide an explicit `namespace` when overriding extensions from a plugin. - -:::info -We recommend that plugin developers share the extension IDs in their plugin documentation, but usually you can infer the ID by following the [naming patterns](./50-naming-patterns.md) documentation. -::: - -### Example - -Imagine you have a plugin with the ID `'search'`, and the plugin provides a page extension that you want to fully override with your own custom component. To do so, you need to create your page extension with an explicit `namespace` option that matches that of the plugin that you want to override, in this case `'search'`. If the existing extension also has an explicit `name` you'd need to set the `name` of your override extension to the same value as well. - -```tsx title="packages/app/src/search.tsx" +```tsx import { createPageExtension, createExtensionOverrides, } from '@backstage/frontend-plugin-api'; -// Creating a custom search page extension -const customSearchPage = createPageExtension({ - // highlight-next-line +const customSearchPage = PageBlueprint({ + // Since this is a standalone extension we need to provide the namespace to match the search plugin namespace: 'search', - // Omitting name since it is the index plugin page - defaultPath: '/search', - loader: () => import('./SearchPage').then(m => m.), + params: { + defaultPath: '/search', + loader: () => + import('./CustomSearchPage').then(m => ), + }, }); export default createExtensionOverrides({ - extensions: [customSearchPage] + extensions: [customSearchPage], }); ``` -Don't forget to configure your overrides in the `createApp` function: +Assuming the above code resides in the `@internal/search-page` package, you can install it in your app like this: ```tsx title="packages/app/src/App.tsx" import { createApp } from '@backstage/frontend-app-api'; -import searchOverrides from './search'; +import searchPageOverride from '@internal/search-page'; const app = createApp({ // highlight-next-line - features: [searchOverrides], + features: [searchPageOverride], }); export default app.createRoot(); ``` - -Now let's talk about the last override case, orphan extensions. - -## Create Standalone Extensions - -Sometimes you just need to quickly create a new extension and not overwrite an app extension or plugin. You can also use overrides to create extensions, but remember that if you want to make this extension available for installation by other users, we recommend providing it via a plugin in a separate package. - -### Example - -Imagine you want to create a page that is currently only used by your application, like an Institutional page, for example. You can use overrides to extend the Backstage app to render it. To do so, simply create a page extension and pass it to the app as an override: - -```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; -import { - createPageExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; - -const app = createApp({ - features: [ - createExtensionOverrides({ - extensions: [ - // highlight-start - createPageExtension({ - name: 'institutional', - defaultPath: '/institutional', - loader: () => - import('./institutional').then(m => ), - }), - // highlight-end - ], - }), - ], -}); - -export default app.createRoot(); -``` - -Note that we are omitting `namespace` when creating the page extension. When we omit `namespace`, we are telling the system the new extension is standalone and not an application or plugin extension! From c894d8d2720c4a67d9319aa6aaa4c73e058bfe30 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 17:00:57 +0200 Subject: [PATCH 300/393] chore: expose react types Signed-off-by: blam --- plugins/app-visualizer/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 6634f3cc1b..4efcf0faf0 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -38,11 +38,11 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1" + "@material-ui/icons": "^4.9.1", + "@types/react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "workspace:^", - "@types/react": "^16.13.1 || ^17.0.0" + "@backstage/cli": "workspace:^" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", From 7673308e828ce927f6cd93e3898195f293b8561b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 17:04:47 +0200 Subject: [PATCH 301/393] Update docs/frontend-system/architecture/25-extension-overrides.md Co-authored-by: Ben Lambert Signed-off-by: Patrik Oldsberg --- docs/frontend-system/architecture/25-extension-overrides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index aa83c32e8b..1cfa71aa44 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -277,7 +277,7 @@ import { createExtensionOverrides, } from '@backstage/frontend-plugin-api'; -const customSearchPage = PageBlueprint({ +const customSearchPage = PageBlueprint.make({ // Since this is a standalone extension we need to provide the namespace to match the search plugin namespace: 'search', params: { From 2d88a424054fd83455d2c4d090ce4d691ed7f89e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 17:28:24 +0200 Subject: [PATCH 302/393] docs/frontend-system: update app migration docs to 1.30 Signed-off-by: Patrik Oldsberg --- .../building-apps/08-migrating.md | 113 ++++++++++-------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index b8fd49c269..ceb3f34c35 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -95,7 +95,7 @@ At this point the contents of your app should be past the initial migration stag ## Migrating `createApp` Options -Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension creator](../architecture/20-extensions.md#extension-creators) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/25-extension-overrides.md#create-standalone-extensions) section for more information. +Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension blueprint](../architecture/23-extension-blueprints.md) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/25-extension-overrides.md#creating-a-standalone-extension-bundle) section for more information. For example, assuming you have a `lightTheme` extension that you want to add to your app, you can use the following: @@ -115,7 +115,7 @@ You can then also add any additional extensions that you may need to create as p ### `apis` -[Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). +[Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `ApiBlueprint` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). For example, the following `apis` configuration: @@ -134,12 +134,15 @@ const app = createApp({ Can be converted to the following extension: ```ts -const scmIntegrationsApi = createApiExtension({ - factory: createApiFactory({ - api: scmIntegrationsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), - }), +const scmIntegrationsApi = ApiBlueprint.make({ + name: 'scm-integrations', + params: { + factory: createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + }, }); ``` @@ -221,7 +224,7 @@ Many app components are now installed as extensions instead using `createCompone The `Router` component is now a built-in extension that you can [override](../architecture/25-extension-overrides.md) using `createRouterExtension`. -The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead. +The Sign-in page is now installed as an extension, created using the `SignInPageBlueprint` instead. For example, the following sign-in page configuration: @@ -246,25 +249,27 @@ const app = createApp({ Can be converted to the following extension: ```tsx -const signInPage = createSignInPageExtension({ - loader: async () => props => - ( - - ), +const signInPage = SignInPageBlueprint.make({ + params: { + loader: async () => props => + ( + + ), + }, }); ``` ### `themes` -Themes are now installed as extensions, using `createThemeExtension`. +Themes are now installed as extensions, created using `ThemeBlueprint`. For example, the following theme configuration: @@ -287,14 +292,19 @@ const app = createApp({ Can be converted to the following extension: ```tsx -const lightTheme = createThemeExtension({ - id: 'light', - title: 'Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), +const lightTheme = ThemeBlueprint.make({ + name: 'light', + params: { + theme: { + id: 'light', + title: 'Light Theme', + variant: 'light', + icon: , + Provider: ({ children }) => ( + + ), + }, + }, }); ``` @@ -360,7 +370,7 @@ const app = createApp({ ### `__experimentalTranslations` -Translations are now installed as extensions, using `createTranslationExtension`. +Translations are now installed as extensions, created using `TranslationBlueprint`. For example, the following translations configuration: @@ -383,11 +393,14 @@ createApp({ Can be converted to the following extension: ```tsx -createTranslationExtension({ - resource: createTranslationMessages({ - ref: catalogTranslationRef, - catalog_page_create_button_title: 'Create Software', - }), +TranslationBlueprint.make({ + name: 'catalog-overrides', + params: { + resource: createTranslationMessages({ + ref: catalogTranslationRef, + catalog_page_create_button_title: 'Create Software', + }), + }, }); ``` @@ -489,17 +502,15 @@ const nav = createExtension({ namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( {/* Sidebar contents from packages/app/src/components/Root.tsx go here */} - + , ), - }; + ]; }, }); ``` @@ -548,7 +559,7 @@ export default app.createRoot( ); ``` -Any app root wrapper needs to be migrated to be an extension, using `createAppRootWrapperExtension`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. +Any app root wrapper needs to be migrated to be an extension, created using `AppRootWrapperBlueprint`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. Here is an example converting the `CustomAppBarrier` into extension: @@ -558,11 +569,13 @@ createApp({ features: [ createExtensionOverrides({ extensions: [ - createAppRootWrapperExtension({ - name: 'CustomAppBarrier', - // Whenever your component uses legacy core packages, wrap it with "compatWrapper" - // e.g. props => compatWrapper() - Component: CustomAppBarrier, + AppRootWrapperBlueprint.make({ + name: 'custom-app-barrier', + params: { + // Whenever your component uses legacy core packages, wrap it with "compatWrapper" + // e.g. props => compatWrapper() + Component: CustomAppBarrier, + }, }), ], }), From 75bf997914d3c97c9651e1af731a5ac004141fa5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 17:32:23 +0200 Subject: [PATCH 303/393] docs/frontend-system: update plugin index docs to 1.30 Signed-off-by: Patrik Oldsberg --- .../building-plugins/01-index.md | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 355de26616..06f1dbc517 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -49,7 +49,7 @@ The plugin ID should be a lowercase dash-separated string, while the plugin inst The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/20-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. -To create a new extension you typically use pre-defined [extension creators](../architecture/20-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. +To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` and `NavItemBlueprint`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. ```tsx title="in src/routes.ts" import { createRouteRef } from '@backstage/frontend-plugin-api'; @@ -71,24 +71,29 @@ import { rootRouteRef } from './routes'; // Note that these extensions aren't exported, only the plugin itself is. // You can export it locally for testing purposes, but don't export it from the plugin package. -const examplePage = createPageExtension({ - routeRef: rootRouteRef, +const examplePage = PageBlueprint.make({ + params: { + routeRef: rootRouteRef, - // This is the default path of this page, but integrators are free to override it - defaultPath: '/example', + // This is the default path of this page, but integrators are free to override it + defaultPath: '/example', - // Page extensions are always dynamically loaded using React.lazy(). - // All of the functionality of this page is implemented in the - // ExamplePage component, which is a regular React component. - // highlight-next-line - loader: () => import('./components/ExamplePage').then(m => ), + // Page extensions are always dynamically loaded using React.lazy(). + // All of the functionality of this page is implemented in the + // ExamplePage component, which is a regular React component. + // highlight-next-line + loader: () => + import('./components/ExamplePage').then(m => ), + }, }); // This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item -const exampleNavItem = createNavItemExtension({ - routeRef: rootRouteRef, - title: 'Example', - icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library +const exampleNavItem = NavItemBlueprint.make({ + params: { + routeRef: rootRouteRef, + title: 'Example', + icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library + }, }); // The same plugin as above, now with the extensions added @@ -157,12 +162,15 @@ import { import { exampleApiRef, DefaultExampleApi } from './api'; // highlight-add-start -const exampleApi = createApiExtension({ - factory: createApiFactory({ - api: exampleApiRef, - deps: {}, - factory: () => new DefaultExampleApi(), - }), +const exampleApi = ApiBlueprint.make({ + name: 'example', + params: { + factory: createApiFactory({ + api: exampleApiRef, + deps: {}, + factory: () => new DefaultExampleApi(), + }), + }, }); // highlight-add-end @@ -187,26 +195,28 @@ export const examplePlugin = createFrontendPlugin({ There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](../../features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page. ```tsx title="in src/plugin.ts - An example entity content extension" -import { createEntityContentExtension } from '@backstage/plugin-catalog-react'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; // Entity content extensions are similar to page extensions in that they are rendered at a route, // although they also have a title to support in-line navigation between the different content. // Just like a page extension the content is lazy loaded, and you can also provide a // route reference if you want to be able to generate a URL that links to the content. -const exampleEntityContent = createEntityContentExtension({ - defaultPath: 'example', - defaultTitle: 'Example', - loader: () => - import('./components/ExampleEntityContent').then(m => ( - - )), +const exampleEntityContent = EntityContentBlueprint.make({ + params: { + defaultPath: 'example', + defaultTitle: 'Example', + loader: () => + import('./components/ExampleEntityContent').then(m => ( + + )), + }, }); export const examplePlugin = createFrontendPlugin({ id: 'example', extensions: [ // highlight-add-next-line - exampleEntityContent + exampleEntityContent, exampleApi, examplePage, exampleNavItem, From 4490d733b503497ee91a1cae76f1419960237991 Mon Sep 17 00:00:00 2001 From: Sydney Achinger <78113809+squid-ney@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:34:15 -0400 Subject: [PATCH 304/393] Redirect: Add documentation. Cleanup (#26041) * Add documentation. Cleanup. Add guardrail not to redirect to current url. --------- Signed-off-by: Sydney Achinger --- .changeset/pink-bobcats-explain.md | 5 ++ docs/features/techdocs/how-to-guides.md | 5 ++ .../TechDocsReaderPageContent/dom.tsx | 2 +- .../TechDocsRedirectNotification.tsx | 67 ++++++++++++++++++ .../TechDocsRedirectNotification/index.ts | 17 +++++ .../transformers/handleMetaRedirects.test.ts | 4 +- .../transformers/handleMetaRedirects.tsx | 70 +++++-------------- .../techdocs/src/reader/transformers/index.ts | 1 + 8 files changed, 115 insertions(+), 56 deletions(-) create mode 100644 .changeset/pink-bobcats-explain.md create mode 100644 plugins/techdocs/src/reader/components/TechDocsRedirectNotification/TechDocsRedirectNotification.tsx create mode 100644 plugins/techdocs/src/reader/components/TechDocsRedirectNotification/index.ts diff --git a/.changeset/pink-bobcats-explain.md b/.changeset/pink-bobcats-explain.md new file mode 100644 index 0000000000..0274e55369 --- /dev/null +++ b/.changeset/pink-bobcats-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Refactor TechDocs' mkdocs-redirects support. diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 3a7cfd97bb..59198e919b 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -841,3 +841,8 @@ metadata: annotations: backstage.io/techdocs-entity: system:default/example ``` + +## How to resolve broken links from moved or renamed pages in your documentation site + +TechDocs supports using the [mkdocs-redirects](https://github.com/mkdocs/mkdocs-redirects/tree/master) plugin to create a redirect map for any TechDocs site. This allows broken links from renamed or moved pages in your site to be redirected to their specified replacement. +TechDocs will notify the user that the page they are trying to access is no longer maintained. Then, they will be redirected. External site redirects are not supported. If an external redirect is provided, the user will instead be redirected to the index page of the documentation site. diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index c686827255..28090cb62c 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -44,9 +44,9 @@ import { copyToClipboard, useSanitizerTransformer, useStylesTransformer, + handleMetaRedirects, } from '../../transformers'; import { useNavigateUrl } from './useNavigateUrl'; -import { handleMetaRedirects } from '../../transformers/handleMetaRedirects'; const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; diff --git a/plugins/techdocs/src/reader/components/TechDocsRedirectNotification/TechDocsRedirectNotification.tsx b/plugins/techdocs/src/reader/components/TechDocsRedirectNotification/TechDocsRedirectNotification.tsx new file mode 100644 index 0000000000..b233549b9e --- /dev/null +++ b/plugins/techdocs/src/reader/components/TechDocsRedirectNotification/TechDocsRedirectNotification.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { makeStyles } from '@material-ui/core/styles'; +import React, { useState } from 'react'; +import Snackbar from '@material-ui/core/Snackbar'; +import Button from '@material-ui/core/Button'; + +type TechDocsRedirectNotificationProps = { + handleButtonClick: () => void; + message: string; + autoHideDuration: number; +}; + +const useStyles = makeStyles(theme => ({ + button: { + color: theme.palette.primary.light, + textDecoration: 'underline', + }, +})); + +export const TechDocsRedirectNotification = ({ + message, + handleButtonClick, + autoHideDuration, +}: TechDocsRedirectNotificationProps) => { + const classes = useStyles(); + const [open, setOpen] = useState(true); + + const handleClose = () => setOpen(false); + + return ( + { + handleClose(); + handleButtonClick(); + }} + > + Redirect now + + } + /> + ); +}; diff --git a/plugins/techdocs/src/reader/components/TechDocsRedirectNotification/index.ts b/plugins/techdocs/src/reader/components/TechDocsRedirectNotification/index.ts new file mode 100644 index 0000000000..fe94a25bd6 --- /dev/null +++ b/plugins/techdocs/src/reader/components/TechDocsRedirectNotification/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { TechDocsRedirectNotification } from './TechDocsRedirectNotification'; diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts index 12a552577a..0be1773846 100644 --- a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts +++ b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts @@ -82,7 +82,9 @@ describe('handleMetaRedirects', () => { ), ).toBeInTheDocument(); jest.runAllTimers(); - expect(navigate).toHaveBeenCalledWith('/docs/default/component/testEntity'); + expect(navigate).toHaveBeenCalledWith( + 'http://localhost/docs/default/component/testEntity', + ); }); it('should navigate to absolute URL if meta redirect tag is present and not external', async () => { diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx index 2d74f4cbf5..04b7b27ba2 100644 --- a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx +++ b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.tsx @@ -16,64 +16,19 @@ import { Transformer } from './transformer'; import { normalizeUrl } from './rewriteDocLinks'; -import Snackbar from '@material-ui/core/Snackbar'; -import React, { useState } from 'react'; +import React from 'react'; import { renderReactElement } from './renderReactElement'; -import Button from '@material-ui/core/Button'; -import { makeStyles } from '@material-ui/core/styles'; - -type RedirectNotificationProps = { - handleButtonClick: () => void; - message: string; - autoHideDuration: number; -}; - -const useStyles = makeStyles(theme => ({ - button: { - color: theme.palette.primary.light, - textDecoration: 'underline', - }, -})); -const RedirectNotification = ({ - message, - handleButtonClick, - autoHideDuration, -}: RedirectNotificationProps) => { - const classes = useStyles(); - const [open, setOpen] = useState(true); - const handleClose = () => { - setOpen(prev => !prev); - }; - - return ( - - Redirect now - - } - /> - ); -}; +import { TechDocsRedirectNotification } from '../components/TechDocsRedirectNotification'; export const handleMetaRedirects = ( navigate: (to: string) => void, entityName: string, ): Transformer => { - const redirectAfterMs = 4000; + const redirectAfterMs = 3000; + const determineRedirectURL = (metaUrl: string) => { const normalizedCurrentUrl = normalizeUrl(window.location.href); - // If metaUrl is relative, it will be resolved with base href. If it is absolute, it will replace the base href when creating URL object. + // When creating URL object, if the metaUrl is relative, it will be resolved with base href. If it is absolute, it will replace the base href. const absoluteRedirectObj = new URL(metaUrl, normalizedCurrentUrl); const isExternalRedirect = absoluteRedirectObj.hostname !== window.location.hostname; @@ -85,9 +40,8 @@ export const handleMetaRedirects = ( 0, indexOfSiteHome + entityName.length, ); - return siteHomePath; + return new URL(siteHomePath, normalizedCurrentUrl).href; } - // The navigate function from dom.tsx is a wrapper around react-router navigate function that helps absolute url redirects. return absoluteRedirectObj.href; }; @@ -97,15 +51,23 @@ export const handleMetaRedirects = ( const metaContentParameters = elem .getAttribute('content') ?.split('url='); + if (!metaContentParameters || metaContentParameters.length < 2) { - continue; + return dom; } + const metaUrl = metaContentParameters[1]; const redirectURL = determineRedirectURL(metaUrl); + + // If the current URL is the same as the redirect URL, do not proceed with the redirect. + if (window.location.href === redirectURL) { + return dom; + } + const container = document.createElement('div'); renderReactElement( - navigate(redirectURL)} autoHideDuration={redirectAfterMs} diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 7df98f1f26..7fdf47b009 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -27,3 +27,4 @@ export * from './simplifyMkdocsFooter'; export * from './onCssReady'; export * from './scrollIntoNavigation'; export * from './transformer'; +export * from './handleMetaRedirects'; From c6072b1083b7b68c81ef9ae69f9695b2eecde329 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 21:33:48 +0200 Subject: [PATCH 305/393] docs/frontend-system: update plugin testing docs for 1.30 Signed-off-by: Patrik Oldsberg --- .../building-plugins/02-testing.md | 135 ++++++------------ 1 file changed, 43 insertions(+), 92 deletions(-) diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md index f665ed0f0c..956fdb8394 100644 --- a/docs/frontend-system/building-plugins/02-testing.md +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -85,6 +85,8 @@ describe('Entity details component', () => { }); ``` +This pattern also works for many other context providers. An important example is the `EntityProvider` from the `@backstage/plugin-catalog-react` package, which you can use to provide a mocked entity context to the component. + ## Testing extensions To facilitate testing of frontend extensions, the `@backstage/frontend-test-utils` package provides a tester class which starts up an entire frontend harness, complete with a number of default features. You can then provide overrides for extensions whose behavior you need to adjust for the test run. @@ -93,7 +95,7 @@ A number of features (frontend extensions and overrides) are also accepted by th ### Single extension -In order to test an extension in isolation, you simply need to pass it into the tester factory, then call the render method on the returned instance: +In order to test an extension in isolation, you can use `createExtensionTester` to create a tester instance and access the element that the extension outputs. This element can then be rendered as usual with `renderInTestApp`: ```tsx import { screen } from '@testing-library/react'; @@ -101,90 +103,49 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { indexPageExtension } from './plugin'; describe('Index page', () => { - it('should render a the index page', () => { - createExtensionTester(indexPageExtension).render(); + it('should render a the index page', async () => { + await renderInTestApp( + createExtensionTester(indexPageExtension).reactElement(), + ); expect(screen.getByText('Index Page')).toBeInTheDocument(); }); }); ``` -### Extension preset +This pattern also allows you to wrap the extension with context providers, such as the `TestApiProvider` that was introduced [above](#testing-react-components). -There are some extensions that rely on other extensions existence, such as a page that links to another page. In that case, you can add more than one extension to the preset of features you want to render in the test, as shown below: +Note that the `.reactElement()` method will look for the `coreExtensionData.reactElement` data in the extension outputs. If that doesn't exist and the extension outputs something else that you want to test, you can access the output data using the `.get(dataRef)` method instead. + +### Multiple extensions + +In some cases you might need to test multiple extensions together, in particular when testing inputs. In this case, you can add more extensions to the tester instance using the `.add(...)` method. It also accepts an optional options object as the second argument, which you can use to provide configuration for the extension instance. ```tsx import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { indexPageExtension, detailsPageExtension } from './plugin'; +import { indexPageExtension, indexPageHeader } from './plugin'; describe('Index page', async () => { - it('should link to the details page', () => { - createExtensionTester(indexPageExtension) - // Adding more extensions to the preset being tested - .add(detailsPageExtension) - .render(); + it('should link to the index page with header', async () => { + const tester = createExtensionTester(indexPageExtension) + // Adding the header to be rendered on the index page + .add(indexPageHeader); - await expect(screen.findByText('Index Page')).toBeInTheDocument(); + await renderInTestApp(tester.reactElement()); - await userEvent.click(screen.getByRole('link', { name: 'See details' })); + await expect(screen.findByText('Index page')).toBeInTheDocument(); + await expect(screen.findByText('Index page header')).toBeInTheDocument(); - await expect( - screen.findByText('Details Page'), - ).resolves.toBeInTheDocument(); + expect( + tester.query(indexPageHeader).get(headerDataRef), + ).toMatchObject(/* ... */); }); }); ``` -### Mocking apis - -If your extensions requires implementation of APIs that aren't wired up by default, you'll have to add overrides to the preset of features being tested: - -```tsx -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { createApiFactory } from '@backestage/core-plugin-api'; -import { - createExtensionOverrides, - configApiRef, - analyticsApiRef, -} from '@backstage/frontend-plugin-api'; -import { - createExtensionTester, - MockConfigApi, - MockAnalyticsApi, -} from '@backstage/frontend-test-utils'; -import { indexPageExtension } from './plugin'; - -describe('Index page', () => { - it('should capture click events in analytics', async () => { - // Mocking the analytics api implementation - const analyticsApiMock = new MockAnalyticsApi(); - - const analyticsApiOverride = createApiExtension({ - factory: createApiFactory({ - api: analyticsApiRef, - factory: () => analyticsApiMock, - }), - }); - - createExtensionTester(indexPageExtension) - // Overriding the analytics api extension - .add(analyticsApiOverride) - .render(); - - await userEvent.click( - await screen.findByRole('link', { name: 'See details' }), - ); - - expect(analyticsApiMock.getEvents()[0]).toMatchObject({ - action: 'click', - subject: 'See details', - }); - }); -}); -``` +When testing multiple extensions you may sometimes want to access the output of other extensions than the main test subject. You can use the `.query(ext)` method to query a different extension that has been added to the tester, either via its ID or passing the extension directly. ### Setting configuration @@ -198,36 +159,26 @@ import { indexPageExtension, detailsPageExtension } from './plugin'; describe('Index page', () => { it('should accepts a custom title via config', async () => { - createExtensionTester(indexPageExtension, { - // Configuration specific of index page - config: { title: 'Custom index' }, - }) - .add(detailsExtensionPage, { - // Configuration specific of details page - config: { title: 'Custom details' }, - }) - .render({ - // Configuration specific of the instance - config: { - app: { - title: 'Custom app', - }, + const tester = createExtensionTester(indexPageExtension, { + // Extension configuration for the index page + config: { title: 'Custom page' }, + }).add(indexPageHeader, { + // Extension configuration for the index page header + config: { title: 'Custom page header' }, + }); + + await renderInTestApp(tester.reactElement(), { + // Global configuration for the app + config: { + app: { + title: 'Custom app', }, - }); + }, + }); - await expect( - screen.findByRole('heading', { name: 'Custom app' }), - ).resolves.toBeInTheDocument(); - - await expect( - screen.findByRole('heading', { name: 'Custom index' }), - ).resolves.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('link', { name: 'See details' })); - - await expect( - screen.findByText('Custom details'), - ).resolves.toBeInTheDocument(); + await expect(screen.findByText('Custom app')).toBeInTheDocument(); + await expect(screen.findByText('Custom page')).toBeInTheDocument(); + await expect(screen.findByText('Custom page header')).toBeInTheDocument(); }); }); ``` From 6d1d1779b158581281956e8f15388f0be0c940cb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 21:43:44 +0200 Subject: [PATCH 306/393] docs/frontend-system: migrate plugin extension types docs to blueprints Signed-off-by: Patrik Oldsberg --- .../frontend-system/building-apps/01-index.md | 2 +- .../building-plugins/01-index.md | 2 +- ...on-types.md => 03-extension-blueprints.md} | 40 +++++++++---------- microsite/sidebars.json | 2 +- plugins/catalog-graph/README-alpha.md | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) rename docs/frontend-system/building-plugins/{03-extension-types.md => 03-extension-blueprints.md} (62%) diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index bb09fb6907..c6a83bb0b7 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -78,7 +78,7 @@ It is possible to enable, disable and configure extensions individually in the ` ### Customize or override built-in extensions -Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See [here](../building-plugins/03-extension-types.md) which types of extensions are available for you to customize your application. +Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See the [extension blueprints](../building-plugins/03-extension-blueprints.md) section for a list of common extension kinds that are available for you to customize and extend your application. ## Use code to customize the app at a more granular level diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 06f1dbc517..1925083ce3 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -229,4 +229,4 @@ export const examplePlugin = createFrontendPlugin({ The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). -For a more complete list of the different types of extensions that you can create for your plugin, see the [extension types](./03-extension-types.md) section. +For a more complete list of the different kinds of extensions that you can create for your plugin, see the [extension blueprints](./03-extension-blueprints.md) section. diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-blueprints.md similarity index 62% rename from docs/frontend-system/building-plugins/03-extension-types.md rename to docs/frontend-system/building-plugins/03-extension-blueprints.md index c7c2e2ab1f..1607050fa6 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-extension-blueprints.md @@ -1,63 +1,63 @@ --- -id: extension-types -title: Frontend System Extension Types -sidebar_label: Extension Types +id: extension-blueprints +title: Frontend System Extension Blueprints +sidebar_label: Extension Blueprints # prettier-ignore -description: Extension types provided by the frontend system and core features +description: Extension blueprints provided by the frontend system and core features --- > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -This section covers many of the [extension types](../architecture/20-extensions.md#extension-creators) available at your disposal when building Backstage frontend plugins. +This section covers many of the [extension blueprints](../architecture/23-extension-blueprints.md) available at your disposal when building Backstage frontend plugins. -## Built-in extension types +## Built-in extension blueprints -These are the extension types provided by the Backstage frontend framework itself. +These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage frontend framework itself. -### Api - [Reference](../../reference/frontend-plugin-api.createapiextension.md) +### Api - [Reference](../../reference/frontend-plugin-api.apiblueprint.md) An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. ### Component - [Reference](../../reference/frontend-plugin-api.createcomponentextension.md) -Components extensions are used to override the component associated with a component reference throughout the app. +Components extensions are used to override the component associated with a component reference throughout the app. This uses an extension creator function rather than a blueprint, but will likely be migrated to a blueprint in the future. -### NavItem - [Reference](../../reference/frontend-plugin-api.createnavitemextension.md) +### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint) Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. -### Page - [Reference](../../reference/frontend-plugin-api.createpageextension.md) +### Page - [Reference](../../reference/frontend-plugin-api.pageblueprint.md) Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. -### SignInPage - [Reference](../../reference/frontend-plugin-api.createsigninpageextension.md) +### SignInPage - [Reference](../../reference/frontend-plugin-api.signinpageblueprint.md) Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. -### Theme - [Reference](../../reference/frontend-plugin-api.createthemeextension.md) +### Theme - [Reference](../../reference/frontend-plugin-api.themeblueprint.md) Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. -### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md) +### Icons - [Reference](../../reference/frontend-plugin-api.ndleblueprint.md) Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app. -### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md) +### Translation - [Reference](../../reference/frontend-plugin-api.translationblueprint.md) Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. -## Core feature extension types +## Core feature extension blueprints -These are the extension types provided by the Backstage core feature plugins. +These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage core feature plugins. ### EntityCard - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) -Creates entity cards to be displayed on the entity pages of the catalog plugin. +Creates entity cards to be displayed on the entity pages of the catalog plugin. Exported as `EntityCardBlueprint`. ### EntityContent - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) -Creates entity content to be displayed on the entity pages of the catalog plugin. +Creates entity content to be displayed on the entity pages of the catalog plugin. Exported as `EntityContentBlueprint`. ### SearchResultListItem - [Reference](https://github.com/backstage/backstage/blob/master/plugins/search-react/api-report-alpha.md) -Creates search result list items for different types of search results, to be displayed in search result lists. +Creates search result list items for different types of search results, to be displayed in search result lists. Exported as `SearchResultListItemBlueprint`. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 8874ede56e..2accf2847b 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -469,7 +469,7 @@ "items": [ "frontend-system/building-plugins/index", "frontend-system/building-plugins/testing", - "frontend-system/building-plugins/extension-types", + "frontend-system/building-plugins/extension-blueprints", "frontend-system/building-plugins/built-in-data-refs", "frontend-system/building-plugins/migrating" ] diff --git a/plugins/catalog-graph/README-alpha.md b/plugins/catalog-graph/README-alpha.md index 52c09c4b50..784066c44f 100644 --- a/plugins/catalog-graph/README-alpha.md +++ b/plugins/catalog-graph/README-alpha.md @@ -126,7 +126,7 @@ The Catalog graphics plugin provides extensions for each of its features, see be #### Catalog Entity Relations Graph Card -An [entity card](https://backstage.io/docs/frontend-system/building-plugins/extension-types#entitycard---reference) extension that renders the relation graph for an entity on the Catalog entity page and has an action that redirects users to the more advanced [relations graph page](#catalog-entity-relations-graph-page). +An [entity card](https://backstage.io/docs/frontend-system/building-plugins/extension-blueprints#entitycard---reference) extension that renders the relation graph for an entity on the Catalog entity page and has an action that redirects users to the more advanced [relations graph page](#catalog-entity-relations-graph-page). | kind | namespace | name | id | Default enabled | | ----------- | ------------- | ---------------- | ------------------------------------- | --------------- | From aefe2bd316cd49926d54104deef401a62f289fe1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 21:46:16 +0200 Subject: [PATCH 307/393] docs/frontend-system: update built-in dataref docs for 1.30 Signed-off-by: Patrik Oldsberg --- .../building-plugins/04-built-in-data-refs.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/frontend-system/building-plugins/04-built-in-data-refs.md b/docs/frontend-system/building-plugins/04-built-in-data-refs.md index d13e734279..a26f6549b7 100644 --- a/docs/frontend-system/building-plugins/04-built-in-data-refs.md +++ b/docs/frontend-system/building-plugins/04-built-in-data-refs.md @@ -24,18 +24,15 @@ The `reactElement` data reference can be used for defining the extension input/o ```tsx import { + createExtension, coreExtensionData, - createExtensionInput, - createPageExtension, } from '@backstage/frontend-plugin-api'; -const homePage = createPageExtension({ - defaultPath: '/home', - routeRef: rootRouteRef, - inputs: { - props: createExtensionInput({ - children: coreExtensionData.reactElement.optional(), - }), +const examplePage = createExtension({ + name: 'example', + output: [coreExtensionData.reactElement], + factor() { + return [coreExtensionData.reactElement(

    Example

    )]; }, }); ``` From 2ffb8a616b2cb34f543444696da4d4c4a4955af1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 21:52:27 +0200 Subject: [PATCH 308/393] docs/frontend-system: migrate plugin migration docs to 1.30 Signed-off-by: Patrik Oldsberg --- .../building-plugins/05-migrating.md | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index 79b259a2e5..9d978869e5 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -8,7 +8,7 @@ description: How to migrate an existing frontend plugin to the new frontend syst This guide allows you to migrate a frontend plugin and its own components, routes, apis to the new frontend system. -The main concept is that routes, components, apis are now extensions. You can use the appropriate extension creators to migrate all of them to extensions. +The main concept is that routes, components, apis are now extensions. You can use the appropriate [extension blueprints](../architecture/23-extension-blueprints.md) to migrate all of them to extensions. ## Migrating the plugin @@ -77,7 +77,7 @@ The code above binds all the extensions to the plugin. _Important_: Make sure to ## Migrating Pages -Pages that were previously created using the `createRoutableExtension` extension function can be migrated to the new Frontend System using the `createPageExtension` extension creator, exported by `@backstage/frontend-plugin-api`. +Pages that were previously created using the `createRoutableExtension` extension function can be migrated to the new Frontend System using the `PageBlueprint` [extension blueprint](../architecture/23-extension-blueprints.md), exported by `@backstage/frontend-plugin-api`. For example, given the following page: @@ -94,28 +94,30 @@ export const FooPage = fooPlugin.provide( it can be migrated as the following: ```tsx -import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -const fooPage = createPageExtension({ - defaultPath: '/foo', - // you can reuse the existing routeRef - // by wrapping into the convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootRouteRef), - // these inputs usually match the props required by the component. - loader: ({ inputs }) => - import('./components/').then(m => - // The compatWrapper utility allows you to use the existing - // legacy frontend utilities used internally by the components. - compatWrapper(), - ), +const fooPage = PageBlueprint.make({ + params: { + defaultPath: '/foo', + // you can reuse the existing routeRef + // by wrapping into the convertLegacyRouteRef. + routeRef: convertLegacyRouteRef(rootRouteRef), + // these inputs usually match the props required by the component. + loader: ({ inputs }) => + import('./components/').then(m => + // The compatWrapper utility allows you to use the existing + // legacy frontend utilities used internally by the components. + compatWrapper(), + ), + }, }); ``` -then add the `fooPage` extension to the plugin: +Then add the `fooPage` extension to the plugin: ```ts title="my-plugin/src/alpha.ts" import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -133,7 +135,7 @@ then add the `fooPage` extension to the plugin: ## Migrating Components -The equivalent utility to replace components created with `createComponentExtension` is `createExtension` from `@backstage/frontend-plugin-api`. However, we recommend searching for a more appropriate extension creator first. +The equivalent utility to replace components created with `createComponentExtension` depends on the context within which the component is used, typically indicated by the naming pattern of the export. Many of these can be migrated to one of the [existing blueprints](03-extension-blueprints.md), but in rare cases it may be necessary to use [`createExtension`](../architecture/20-extensions.md#creating-an-extension) directly. ## Migrating APIs @@ -182,7 +184,7 @@ const exampleWorkApi = createApiFactory({ The major changes we'll make are - Change the old `@backstage/core-plugin-api` imports to the new `@backstage/frontend-plugin-api` package as per the top section of this guide -- Wrap the existing API factory in a `createApiExtension` +- Wrap the existing API factory in a `ApiBlueprint` The end result, after simplifying imports and cleaning up a bit, might look like this: @@ -190,17 +192,19 @@ The end result, after simplifying imports and cleaning up a bit, might look like import { storageApiRef, createApiFactory, - createApiExtension, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; import { workApiRef } from '@internal/plugin-example-react'; import { WorkImpl } from './WorkImpl'; -const exampleWorkApi = createApiExtension({ - factory: createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => new WorkImpl({ storageApi }), - }), +const exampleWorkApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), + }), + }, }); ``` From 9058fd4e165d086dcf2fc9fb48240b6430653219 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 22:08:19 +0200 Subject: [PATCH 309/393] docs/frontend-system: update API creation docs for 1.30 Signed-off-by: Patrik Oldsberg --- .../utility-apis/02-creating.md | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index bd0b71120d..01c21fe656 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -45,7 +45,7 @@ The plugin itself now wants to provide this API and its default implementation, ```tsx title="in @internal/plugin-example" import { - createApiExtension, + ApiBlueprint, createApiFactory, createFrontendPlugin, storageApiRef, @@ -62,14 +62,17 @@ class WorkImpl implements WorkApi { } } -const exampleWorkApi = createApiExtension({ - factory: createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => { - return new WorkImpl({ storageApi }); - }, - }), +const workApi = ApiBlueprint.make({ + name: 'work', + params: { + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + return new WorkImpl({ storageApi }); + }, + }), + }, }); /** @@ -86,43 +89,37 @@ For illustration we make a skeleton implementation class and the API extension a The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function. -The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. Check out [the naming patterns doc](../architecture/50-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. +The extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, a `/` separator, and lastly the name we used of the extension. In this case we end up with `api:example/work`. Check out [the naming patterns doc](../architecture/50-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. In case there is a single API that is a central to the functionality of the plugin, most typically an API client, you can choose to omit the name of the extension so that you end up with just `api:`. ## Adding configurability -Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's make the required additions to our original work example API. +Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's refactory the example above to also accept configuration, which will require us to use the [override method of the blueprint](../architecture/23-extension-blueprints.md#creating-an-extension-from-a-blueprint-with-overrides). ```tsx title="in @internal/plugin-example" -/* highlight-add-next-line */ -import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; - -const exampleWorkApi = createApiExtension({ - /* highlight-add-start */ - api: workApiRef, - configSchema: createSchemaFromZod(z => - z.object({ - goSlow: z.boolean().default(false), - }), - ), - /* highlight-add-end */ - /* highlight-remove-next-line */ - factory: createApiFactory({ - /* highlight-add-next-line */ - factory: ({ config }) => createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => { - /* highlight-add-start */ - if (config.goSlow) { - /* ... */ - } - /* highlight-add-end */ +const exampleWorkApi = ApiBlueprint.makeWithOverrides({ + config: { + schema: { + goSlow: z => z.boolean().default(false), }, - }), + }, + factory(originalFactory, { config }) { + return originalFactory({ + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + return new WorkImpl({ + storageApi, + goSlow: config.goSlow, + }); + }, + }), + }); + }, }); ``` -We wanted users to be able to set a `goSlow` extension config parameter for our API instances. So we passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual extension config values will then be passed in a type safe manner in to the `factory` which is now a callback, wherein we can do what we wish with them. When changing to the callback form, we also had to add a top level `api: workApiRef` under `createApiExtension`. +We wanted users to be able to set a `goSlow` extension config parameter for our API instances, which we declared in our new configuration schema. The actual extension config values will then be passed in a type safe manner in to the blueprint `factory`, wherein we can use them to create our API factory and pass as our blueprint parameters. Note that the expression "extension config" as used here, is _not_ the same thing as the `configApi` which gives you access to the full app-config. The extension config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the Configuring section](./04-configuring.md). @@ -130,11 +127,11 @@ Note also that the extension config schema contained a default value fo the `goS ## Adding inputs -Inputs are added to Utility APIs in the same way as other extension types: +Inputs are added to Utility APIs in the same way as other extension blueprints: -- Declaring a set of `inputs` on your extension -- If needed, create custom extension data types to be used in those inputs -- If needed, export an extension creator function for creating that particular attachment type +- Use `.makeWithOverrides` and declare a set of `inputs` for your extension. +- If needed, create custom extension data types to be used in those inputs. +- If needed, create and export an [extension blueprint](../architecture/23-extension-blueprints.md#creating-an-extension-blueprint) for creating that particular attachment type. This is a power use case and not very commonly used. From 1fde8285ecbc29c9930e33438c88fc4471d98d34 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 22:11:30 +0200 Subject: [PATCH 310/393] docs/frontend-system: update the rest of the API docs for 1.30 Signed-off-by: Patrik Oldsberg --- .../utility-apis/03-consuming.md | 26 ++++++++++--------- .../utility-apis/04-configuring.md | 8 +++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index 5c53c8617e..bdf3b8dcdb 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -46,23 +46,25 @@ Your utility APIs can depend on other utility APIs in their factories. You do th ```tsx import { configApiRef, - createApiExtension, + ApiBlueprint, createApiFactory, discoveryApiRef, } from '@backstage/frontend-plugin-api'; import { MyApiImpl } from './MyApiImpl'; -const myApi = createApiExtension({ - factory: createApiFactory({ - api: myApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - }, - factory: ({ configApi, discoveryApi }) => { - return new MyApiImpl({ configApi, discoveryApi }); - }, - }), +const myApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: myApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + }, + factory: ({ configApi, discoveryApi }) => { + return new MyApiImpl({ configApi, discoveryApi }); + }, + }), + }, }); ``` diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index d22e10011b..e403104506 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -49,13 +49,13 @@ class CustomWorkImpl implements WorkApi { const myOverrides = createExtensionOverrides({ extensions: [ - createApiExtension({ - api: workApiRef, - factory: () => - createApiFactory({ + ApiBlueprint.make({ + params: { + factory: createApiFactory({ api: workApiRef, factory: () => new CustomWorkImpl(), }), + }, }), ], }); From d52ed1a5627a246f850a11391597bba1d8a76410 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Aug 2024 22:44:38 +0200 Subject: [PATCH 311/393] frontend-plugin-api: store extension ID parts in an object type parameter Signed-off-by: Patrik Oldsberg --- .../app-next-example-plugin/api-report.md | 8 +- packages/frontend-plugin-api/api-report.md | 206 ++++++++++------- .../src/wiring/createExtension.ts | 76 +++--- .../src/wiring/createExtensionBlueprint.ts | 44 ++-- .../wiring/resolveExtensionDefinition.test.ts | 8 +- .../src/wiring/resolveExtensionDefinition.ts | 8 +- plugins/api-docs/api-report-alpha.md | 88 ++++--- plugins/app-visualizer/api-report.md | 16 +- plugins/catalog-graph/api-report-alpha.md | 16 +- plugins/catalog-import/api-report-alpha.md | 16 +- plugins/catalog-react/api-report-alpha.md | 32 ++- plugins/catalog/api-report-alpha.md | 216 +++++++++++------- plugins/devtools/api-report-alpha.md | 24 +- plugins/home/api-report-alpha.md | 8 +- plugins/kubernetes/api-report-alpha.md | 40 ++-- plugins/org/api-report-alpha.md | 32 ++- plugins/scaffolder/api-report-alpha.md | 24 +- plugins/search-react/api-report-alpha.md | 16 +- plugins/search/api-report-alpha.md | 48 ++-- plugins/techdocs/api-report-alpha.md | 48 ++-- plugins/user-settings/api-report-alpha.md | 24 +- 21 files changed, 622 insertions(+), 376 deletions(-) diff --git a/packages/app-next-example-plugin/api-report.md b/packages/app-next-example-plugin/api-report.md index 3c8b740e1c..4698edec6b 100644 --- a/packages/app-next-example-plugin/api-report.md +++ b/packages/app-next-example-plugin/api-report.md @@ -36,9 +36,11 @@ const examplePlugin: BackstagePlugin< } >, {}, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index c736f3b7d0..d19e60612d 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -191,9 +191,11 @@ export type AnyRoutes = { // @public export const ApiBlueprint: ExtensionBlueprint< - 'api', - undefined, - undefined, + { + kind: 'api'; + namespace: undefined; + name: undefined; + }, { factory: AnyApiFactory; }, @@ -263,9 +265,11 @@ export interface AppNodeSpec { // @public export const AppRootElementBlueprint: ExtensionBlueprint< - 'app-root-element', - undefined, - undefined, + { + kind: 'app-root-element'; + namespace: undefined; + name: undefined; + }, { element: JSX.Element | (() => JSX.Element); }, @@ -278,9 +282,11 @@ export const AppRootElementBlueprint: ExtensionBlueprint< // @public export const AppRootWrapperBlueprint: ExtensionBlueprint< - 'app-root-wrapper', - undefined, - undefined, + { + kind: 'app-root-wrapper'; + namespace: undefined; + name: undefined; + }, { Component: ComponentType>; }, @@ -475,9 +481,11 @@ export function createApiExtension< TConfig, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @public (undocumented) @@ -579,9 +587,11 @@ export function createComponentExtension< TConfig, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @public (undocumented) @@ -642,9 +652,11 @@ export function createExtension< >, UOutput, TInputs, - string | undefined extends TKind ? undefined : TKind, - string | undefined extends TNamespace ? undefined : TNamespace, - string | undefined extends TName ? undefined : TName + { + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; + } >; // @public @deprecated (undocumented) @@ -698,9 +710,11 @@ export function createExtensionBlueprint< TDataRefs >, ): ExtensionBlueprint< - TKind, - TNamespace, - TName, + { + kind: TKind; + namespace: TNamespace; + name: TName; + }, TParams, UOutput, string extends keyof TInputs ? {} : TInputs, @@ -928,9 +942,11 @@ export function createNavItemExtension(options: { }, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @public (undocumented) @@ -958,9 +974,11 @@ export function createNavLogoExtension(options: { unknown, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @public (undocumented) @@ -1109,16 +1127,16 @@ export function createSubRouteRef< }): MakeSubRouteRef, ParentParams>; // @public @deprecated (undocumented) -export function createThemeExtension( - theme: AppTheme, -): ExtensionDefinition< +export function createThemeExtension(theme: AppTheme): ExtensionDefinition< unknown, unknown, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @public @deprecated (undocumented) @@ -1140,9 +1158,11 @@ export function createTranslationExtension(options: { unknown, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @public @deprecated (undocumented) @@ -1199,9 +1219,11 @@ export interface Extension { // @public (undocumented) export interface ExtensionBlueprint< - TKind extends string, - TNamespace extends string | undefined, - TName extends string | undefined, + TIdParts extends { + kind: string; + namespace?: string; + name?: string; + }, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -1243,9 +1265,13 @@ export interface ExtensionBlueprint< TConfigInput, UOutput, TInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName + { + kind: TIdParts['kind']; + namespace: string | undefined extends TNewNamespace + ? TIdParts['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? TIdParts['name'] : TNewName; + } >; makeWithOverrides< TNewNamespace extends string | undefined, @@ -1321,9 +1347,13 @@ export interface ExtensionBlueprint< TConfigInput, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName + { + kind: TIdParts['kind']; + namespace: string | undefined extends TNewNamespace + ? TIdParts['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? TIdParts['name'] : TNewName; + } >; } @@ -1419,9 +1449,15 @@ export interface ExtensionDefinition< } >; } = {}, - TKind extends string | undefined = string | undefined, - TNamespace extends string | undefined = string | undefined, - TName extends string | undefined = string | undefined, + TIdParts extends { + kind?: string; + namespace?: string; + name?: string; + } = { + kind?: string; + namespace?: string; + name?: string; + }, > { // (undocumented) $$type: '@backstage/ExtensionDefinition'; @@ -1435,11 +1471,11 @@ export interface ExtensionDefinition< // (undocumented) readonly disabled: boolean; // (undocumented) - readonly kind?: TKind; + readonly kind?: TIdParts['kind']; // (undocumented) - readonly name?: TName; + readonly name?: TIdParts['name']; // (undocumented) - readonly namespace?: TNamespace; + readonly namespace?: TIdParts['namespace']; // (undocumented) override< TExtensionConfigSchema extends { @@ -1509,9 +1545,7 @@ export interface ExtensionDefinition< TConfigInput, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, - TKind, - TNamespace, - TName + TIdParts >; } @@ -1591,9 +1625,11 @@ export { googleAuthApiRef }; // @public (undocumented) export const IconBundleBlueprint: ExtensionBlueprint< - 'icon-bundle', - 'app', - undefined, + { + kind: 'icon-bundle'; + namespace: 'app'; + name: undefined; + }, { icons: { [x: string]: IconComponent; @@ -1705,9 +1741,11 @@ export { microsoftAuthApiRef }; // @public export const NavItemBlueprint: ExtensionBlueprint< - 'nav-item', - undefined, - undefined, + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + }, { title: string; icon: IconComponent_2; @@ -1740,9 +1778,11 @@ export const NavItemBlueprint: ExtensionBlueprint< // @public export const NavLogoBlueprint: ExtensionBlueprint< - 'nav-logo', - undefined, - undefined, + { + kind: 'nav-logo'; + namespace: undefined; + name: undefined; + }, { logoIcon: JSX.Element; logoFull: JSX.Element; @@ -1790,9 +1830,11 @@ export { OpenIdConnectApi }; // @public export const PageBlueprint: ExtensionBlueprint< - 'page', - undefined, - undefined, + { + kind: 'page'; + namespace: undefined; + name: undefined; + }, { defaultPath: string; loader: () => Promise; @@ -1951,9 +1993,11 @@ export type RouteFunc = ( // @public (undocumented) export const RouterBlueprint: ExtensionBlueprint< - 'app-router-component', - undefined, - undefined, + { + kind: 'app-router-component'; + namespace: undefined; + name: undefined; + }, { Component: ComponentType>; }, @@ -2014,9 +2058,11 @@ export { SessionState }; // @public export const SignInPageBlueprint: ExtensionBlueprint< - 'sign-in-page', - undefined, - undefined, + { + kind: 'sign-in-page'; + namespace: undefined; + name: undefined; + }, { loader: () => Promise>; }, @@ -2057,9 +2103,11 @@ export interface SubRouteRef< // @public export const ThemeBlueprint: ExtensionBlueprint< - 'theme', - 'app', - undefined, + { + kind: 'theme'; + namespace: 'app'; + name: undefined; + }, { theme: AppTheme; }, @@ -2074,9 +2122,11 @@ export const ThemeBlueprint: ExtensionBlueprint< // @public export const TranslationBlueprint: ExtensionBlueprint< - 'translation', - undefined, - undefined, + { + kind: 'translation'; + namespace: undefined; + name: undefined; + }, { resource: TranslationResource | TranslationMessages; }, diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 86fcdc56ee..66b908f236 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -218,14 +218,20 @@ export interface ExtensionDefinition< { optional: boolean; singleton: boolean } >; } = {}, - TKind extends string | undefined = string | undefined, - TNamespace extends string | undefined = string | undefined, - TName extends string | undefined = string | undefined, + TIdParts extends { + kind?: string; + namespace?: string; + name?: string; + } = { + kind?: string; + namespace?: string; + name?: string; + }, > { $$type: '@backstage/ExtensionDefinition'; - readonly kind?: TKind; - readonly namespace?: TNamespace; - readonly name?: TName; + readonly kind?: TIdParts['kind']; + readonly namespace?: TIdParts['namespace']; + readonly name?: TIdParts['name']; readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; @@ -292,9 +298,7 @@ export interface ExtensionDefinition< TConfigInput, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, - TKind, - TNamespace, - TName + TIdParts >; } @@ -309,18 +313,16 @@ export type InternalExtensionDefinition< { optional: boolean; singleton: boolean } >; } = {}, - TKind extends string | undefined = string | undefined, - TNamespace extends string | undefined = string | undefined, - TName extends string | undefined = string | undefined, -> = ExtensionDefinition< - TConfig, - TConfigInput, - UOutput, - TInputs, - TKind, - TNamespace, - TName -> & + TIdParts extends { + kind?: string; + namespace?: string; + name?: string; + } = { + kind?: string; + namespace?: string; + name?: string; + }, +> = ExtensionDefinition & ( | { readonly version: 'v1'; @@ -411,9 +413,11 @@ export function createExtension< >, UOutput, TInputs, - string | undefined extends TKind ? undefined : TKind, - string | undefined extends TNamespace ? undefined : TNamespace, - string | undefined extends TName ? undefined : TName + { + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; + } >; /** * @public @@ -482,9 +486,11 @@ export function createExtension< >), UOutput, TInputs, - TKind, - TNamespace, - TName + { + kind: TKind; + namespace: TNamespace; + name: TName; + } > { if ('configSchema' in options && 'config' in options) { throw new Error(`Cannot provide both configSchema and config.schema`); @@ -596,9 +602,11 @@ export function createExtension< >, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, - TKind, - TNamespace, - TName + { + kind: TKind; + namespace: TNamespace; + name: TName; + } > => { if (!Array.isArray(options.output)) { throw new Error( @@ -697,8 +705,10 @@ export function createExtension< >), UOutput, TInputs, - TKind, - TNamespace, - TName + { + kind: TKind; + namespace: TNamespace; + name: TName; + } >; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index d1fc75bca6..c27893489d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -85,9 +85,11 @@ export type CreateExtensionBlueprintOptions< * @public */ export interface ExtensionBlueprint< - TKind extends string, - TNamespace extends string | undefined, - TName extends string | undefined, + TIdParts extends { + kind: string; + namespace?: string; + name?: string; + }, TParams, UOutput extends AnyExtensionDataRef, TInputs extends { @@ -116,9 +118,13 @@ export interface ExtensionBlueprint< TConfigInput, UOutput, TInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName + { + kind: TIdParts['kind']; + namespace: string | undefined extends TNewNamespace + ? TIdParts['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? TIdParts['name'] : TNewName; + } >; /** @@ -195,9 +201,13 @@ export interface ExtensionBlueprint< TConfigInput, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName + { + kind: TIdParts['kind']; + namespace: string | undefined extends TNewNamespace + ? TIdParts['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? TIdParts['name'] : TNewName; + } >; } @@ -422,9 +432,11 @@ export function createExtensionBlueprint< TDataRefs >, ): ExtensionBlueprint< - TKind, - TNamespace, - TName, + { + kind: TKind; + namespace: TNamespace; + name: TName; + }, TParams, UOutput, string extends keyof TInputs ? {} : TInputs, @@ -441,9 +453,11 @@ export function createExtensionBlueprint< TDataRefs > { return new ExtensionBlueprintImpl(options) as ExtensionBlueprint< - TKind, - TNamespace, - TName, + { + kind: TKind; + namespace: TNamespace; + name: TName; + }, TParams, UOutput, string extends keyof TInputs ? {} : TInputs, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index 5a42e9100e..3b43cd6518 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -108,7 +108,13 @@ describe('ResolveExtensionId', () => { TKind extends string | undefined, TNamespace extends string | undefined, TName extends string | undefined, - > = ExtensionDefinition; + > = ExtensionDefinition< + any, + any, + any, + any, + { kind: TKind; namespace: TNamespace; name: TName } + >; const id1: 'k:ns' = {} as ResolveExtensionId< NamedExtension<'k', 'ns', undefined>, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 35bf9c6b05..9cd4a76882 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -103,9 +103,11 @@ export type ResolveExtensionId< any, any, any, - infer IKind, - infer INamespace, - infer IName + { + kind: infer IKind extends string | undefined; + namespace: infer INamespace extends string | undefined; + name: infer IName extends string | undefined; + } > ? [string | undefined] extends [IKind | INamespace | IName] ? never diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md index e0831b2d98..c7ab4eb9e0 100644 --- a/plugins/api-docs/api-report-alpha.md +++ b/plugins/api-docs/api-report-alpha.md @@ -38,18 +38,22 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; 'api:api-docs/config': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'config' + { + kind: 'api'; + namespace: undefined; + name: 'config'; + } >; 'page:api-docs': ExtensionDefinition< { @@ -84,9 +88,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; 'entity-card:api-docs/has-apis': ExtensionDefinition< { @@ -115,9 +121,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'has-apis' + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-apis'; + } >; 'entity-card:api-docs/definition': ExtensionDefinition< { @@ -146,9 +154,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'definition' + { + kind: 'entity-card'; + namespace: undefined; + name: 'definition'; + } >; 'entity-card:api-docs/consumed-apis': ExtensionDefinition< { @@ -177,9 +187,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'consumed-apis' + { + kind: 'entity-card'; + namespace: undefined; + name: 'consumed-apis'; + } >; 'entity-card:api-docs/provided-apis': ExtensionDefinition< { @@ -208,9 +220,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'provided-apis' + { + kind: 'entity-card'; + namespace: undefined; + name: 'provided-apis'; + } >; 'entity-card:api-docs/consuming-components': ExtensionDefinition< { @@ -239,9 +253,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'consuming-components' + { + kind: 'entity-card'; + namespace: undefined; + name: 'consuming-components'; + } >; 'entity-card:api-docs/providing-components': ExtensionDefinition< { @@ -270,9 +286,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'providing-components' + { + kind: 'entity-card'; + namespace: undefined; + name: 'providing-components'; + } >; 'entity-content:api-docs/definition': ExtensionDefinition< { @@ -314,9 +332,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-content', - undefined, - 'definition' + { + kind: 'entity-content'; + namespace: undefined; + name: 'definition'; + } >; 'entity-content:api-docs/apis': ExtensionDefinition< { @@ -358,9 +378,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-content', - undefined, - 'apis' + { + kind: 'entity-content'; + namespace: undefined; + name: 'apis'; + } >; } >; diff --git a/plugins/app-visualizer/api-report.md b/plugins/app-visualizer/api-report.md index cab8b113b6..e01040a22a 100644 --- a/plugins/app-visualizer/api-report.md +++ b/plugins/app-visualizer/api-report.md @@ -37,9 +37,11 @@ const visualizerPlugin: BackstagePlugin< } >, {}, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; 'nav-item:app-visualizer': ExtensionDefinition< {}, @@ -54,9 +56,11 @@ const visualizerPlugin: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index 03ee352227..6ab438def3 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -87,9 +87,11 @@ const _default: BackstagePlugin< } >; }, - 'entity-card', - undefined, - 'relations' + { + kind: 'entity-card'; + namespace: undefined; + name: 'relations'; + } >; 'page:catalog-graph': ExtensionDefinition< { @@ -148,9 +150,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/catalog-import/api-report-alpha.md b/plugins/catalog-import/api-report-alpha.md index 3129d13e07..9f82f39266 100644 --- a/plugins/catalog-import/api-report-alpha.md +++ b/plugins/catalog-import/api-report-alpha.md @@ -23,9 +23,11 @@ const _default: BackstagePlugin< {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - undefined + { + kind: 'api'; + namespace: undefined; + name: undefined; + } >; 'page:catalog-import': ExtensionDefinition< { @@ -48,9 +50,11 @@ const _default: BackstagePlugin< } >, {}, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index c5dc632946..6f801ed45f 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -123,9 +123,11 @@ export function createEntityCardExtension< TConfig, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @alpha @deprecated (undocumented) @@ -162,16 +164,20 @@ export function createEntityContentExtension< }, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @alpha export const EntityCardBlueprint: ExtensionBlueprint< - 'entity-card', - undefined, - undefined, + { + kind: 'entity-card'; + namespace: undefined; + name: undefined; + }, { loader: () => Promise; filter?: string | ((entity: Entity) => boolean) | undefined; @@ -214,9 +220,11 @@ export const EntityCardBlueprint: ExtensionBlueprint< // @alpha export const EntityContentBlueprint: ExtensionBlueprint< - 'entity-content', - undefined, - undefined, + { + kind: 'entity-content'; + namespace: undefined; + name: undefined; + }, { loader: () => Promise; defaultPath: string; diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 386024e0c0..560f179efa 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -27,9 +27,11 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha export const CatalogFilterBlueprint: ExtensionBlueprint< - 'catalog-filter', - undefined, - undefined, + { + kind: 'catalog-filter'; + namespace: undefined; + name: undefined; + }, { loader: () => Promise; }, @@ -140,9 +142,11 @@ export function createCatalogFilterExtension< TConfig, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @alpha (undocumented) @@ -182,27 +186,33 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; 'api:catalog/starred-entities': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'starred-entities' + { + kind: 'api'; + namespace: undefined; + name: 'starred-entities'; + } >; 'api:catalog/entity-presentation': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'entity-presentation' + { + kind: 'api'; + namespace: undefined; + name: 'entity-presentation'; + } >; 'entity-card:catalog/about': ExtensionDefinition< { @@ -227,9 +237,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'about' + { + kind: 'entity-card'; + namespace: undefined; + name: 'about'; + } >; 'entity-card:catalog/links': ExtensionDefinition< { @@ -254,9 +266,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'links' + { + kind: 'entity-card'; + namespace: undefined; + name: 'links'; + } >; 'entity-card:catalog/labels': ExtensionDefinition< { @@ -281,9 +295,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'labels' + { + kind: 'entity-card'; + namespace: undefined; + name: 'labels'; + } >; 'entity-card:catalog/depends-on-components': ExtensionDefinition< { @@ -308,9 +324,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'depends-on-components' + { + kind: 'entity-card'; + namespace: undefined; + name: 'depends-on-components'; + } >; 'entity-card:catalog/depends-on-resources': ExtensionDefinition< { @@ -335,9 +353,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'depends-on-resources' + { + kind: 'entity-card'; + namespace: undefined; + name: 'depends-on-resources'; + } >; 'entity-card:catalog/has-components': ExtensionDefinition< { @@ -362,9 +382,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'has-components' + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-components'; + } >; 'entity-card:catalog/has-resources': ExtensionDefinition< { @@ -389,9 +411,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'has-resources' + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-resources'; + } >; 'entity-card:catalog/has-subcomponents': ExtensionDefinition< { @@ -416,9 +440,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'has-subcomponents' + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-subcomponents'; + } >; 'entity-card:catalog/has-subdomains': ExtensionDefinition< { @@ -443,9 +469,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'has-subdomains' + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-subdomains'; + } >; 'entity-card:catalog/has-systems': ExtensionDefinition< { @@ -470,9 +498,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'has-systems' + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-systems'; + } >; 'entity-content:catalog/overview': ExtensionDefinition< { @@ -536,18 +566,22 @@ const _default: BackstagePlugin< } >; }, - 'entity-content', - undefined, - 'overview' + { + kind: 'entity-content'; + namespace: undefined; + name: 'overview'; + } >; 'catalog-filter:catalog/tag': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'catalog-filter', - undefined, - 'tag' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'tag'; + } >; 'catalog-filter:catalog/kind': ExtensionDefinition< { @@ -566,18 +600,22 @@ const _default: BackstagePlugin< } >; }, - 'catalog-filter', - undefined, - 'kind' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'kind'; + } >; 'catalog-filter:catalog/type': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'catalog-filter', - undefined, - 'type' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'type'; + } >; 'catalog-filter:catalog/mode': ExtensionDefinition< { @@ -596,36 +634,44 @@ const _default: BackstagePlugin< } >; }, - 'catalog-filter', - undefined, - 'mode' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'mode'; + } >; 'catalog-filter:catalog/namespace': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'catalog-filter', - undefined, - 'namespace' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'namespace'; + } >; 'catalog-filter:catalog/lifecycle': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'catalog-filter', - undefined, - 'lifecycle' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'lifecycle'; + } >; 'catalog-filter:catalog/processing-status': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'catalog-filter', - undefined, - 'processing-status' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'processing-status'; + } >; 'catalog-filter:catalog/list': ExtensionDefinition< { @@ -644,9 +690,11 @@ const _default: BackstagePlugin< } >; }, - 'catalog-filter', - undefined, - 'list' + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'list'; + } >; 'page:catalog': ExtensionDefinition< { @@ -677,9 +725,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; 'page:catalog/entity': ExtensionDefinition< { @@ -737,9 +787,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - 'entity' + { + kind: 'page'; + namespace: undefined; + name: 'entity'; + } >; 'search-result-list-item:catalog': ExtensionDefinition< { @@ -757,9 +809,11 @@ const _default: BackstagePlugin< {} >, {}, - 'search-result-list-item', - undefined, - undefined + { + kind: 'search-result-list-item'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/devtools/api-report-alpha.md b/plugins/devtools/api-report-alpha.md index f5ca1f98c1..888faa29c7 100644 --- a/plugins/devtools/api-report-alpha.md +++ b/plugins/devtools/api-report-alpha.md @@ -24,9 +24,11 @@ const _default: BackstagePlugin< {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - undefined + { + kind: 'api'; + namespace: undefined; + name: undefined; + } >; 'page:devtools': ExtensionDefinition< { @@ -49,9 +51,11 @@ const _default: BackstagePlugin< } >, {}, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; 'nav-item:devtools': ExtensionDefinition< {}, @@ -66,9 +70,11 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/home/api-report-alpha.md b/plugins/home/api-report-alpha.md index 2efdc19b79..e32412168b 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -62,9 +62,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md index 6b53443e2a..15f7efaf1b 100644 --- a/plugins/kubernetes/api-report-alpha.md +++ b/plugins/kubernetes/api-report-alpha.md @@ -38,9 +38,11 @@ const _default: BackstagePlugin< } >, {}, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; 'entity-content:kubernetes/kubernetes': ExtensionDefinition< { @@ -78,36 +80,44 @@ const _default: BackstagePlugin< } >, {}, - 'entity-content', - undefined, - 'kubernetes' + { + kind: 'entity-content'; + namespace: undefined; + name: 'kubernetes'; + } >; 'api:kubernetes/proxy': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'proxy' + { + kind: 'api'; + namespace: undefined; + name: 'proxy'; + } >; 'api:kubernetes/auth-providers': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'auth-providers' + { + kind: 'api'; + namespace: undefined; + name: 'auth-providers'; + } >; 'api:kubernetes/cluster-link-formatter': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'cluster-link-formatter' + { + kind: 'api'; + namespace: undefined; + name: 'cluster-link-formatter'; + } >; } >; diff --git a/plugins/org/api-report-alpha.md b/plugins/org/api-report-alpha.md index d96825c601..1187c8f0e3 100644 --- a/plugins/org/api-report-alpha.md +++ b/plugins/org/api-report-alpha.md @@ -44,9 +44,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'group-profile' + { + kind: 'entity-card'; + namespace: undefined; + name: 'group-profile'; + } >; 'entity-card:org/members-list': ExtensionDefinition< { @@ -75,9 +77,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'members-list' + { + kind: 'entity-card'; + namespace: undefined; + name: 'members-list'; + } >; 'entity-card:org/ownership': ExtensionDefinition< { @@ -106,9 +110,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'ownership' + { + kind: 'entity-card'; + namespace: undefined; + name: 'ownership'; + } >; 'entity-card:org/user-profile': ExtensionDefinition< { @@ -137,9 +143,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-card', - undefined, - 'user-profile' + { + kind: 'entity-card'; + namespace: undefined; + name: 'user-profile'; + } >; } >; diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index 4025174c55..508fa17fb2 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -48,9 +48,11 @@ const _default: BackstagePlugin< {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - undefined + { + kind: 'api'; + namespace: undefined; + name: undefined; + } >; 'page:scaffolder': ExtensionDefinition< { @@ -73,9 +75,11 @@ const _default: BackstagePlugin< } >, {}, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; 'nav-item:scaffolder': ExtensionDefinition< {}, @@ -90,9 +94,11 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index 0126fdc590..bce620c378 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -31,9 +31,11 @@ export function createSearchResultListItemExtension< TConfig, never, never, - string | undefined, - string | undefined, - string | undefined + { + kind?: string | undefined; + namespace?: string | undefined; + name?: string | undefined; + } >; // @alpha @deprecated (undocumented) @@ -82,9 +84,11 @@ export type SearchResultItemExtensionPredicate = ( // @alpha export const SearchResultListItemBlueprint: ExtensionBlueprint< - 'search-result-list-item', - undefined, - undefined, + { + kind: 'search-result-list-item'; + namespace: undefined; + name: undefined; + }, SearchResultListItemBlueprintParams, ConfigurableExtensionDataRef< { diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index 8a47cb41fc..6aa956f131 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -27,9 +27,11 @@ const _default: BackstagePlugin< {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - undefined + { + kind: 'api'; + namespace: undefined; + name: undefined; + } >; 'nav-item:search': ExtensionDefinition< {}, @@ -44,9 +46,11 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; 'page:search': ExtensionDefinition< { @@ -88,9 +92,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; @@ -102,9 +108,11 @@ export const searchApi: ExtensionDefinition< {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - undefined + { + kind: 'api'; + namespace: undefined; + name: undefined; + } >; // @alpha (undocumented) @@ -121,9 +129,11 @@ export const searchNavItem: ExtensionDefinition< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; // @alpha (undocumented) @@ -163,9 +173,11 @@ export const searchPage: ExtensionDefinition< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; // (No @packageDocumentation comment for this package) diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index 14a2400534..27d09e53e0 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -43,18 +43,22 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; 'api:techdocs/storage': ExtensionDefinition< {}, {}, ConfigurableExtensionDataRef, {}, - 'api', - undefined, - 'storage' + { + kind: 'api'; + namespace: undefined; + name: 'storage'; + } >; 'search-result-list-item:techdocs': ExtensionDefinition< { @@ -90,9 +94,11 @@ const _default: BackstagePlugin< } >; }, - 'search-result-list-item', - undefined, - undefined + { + kind: 'search-result-list-item'; + namespace: undefined; + name: undefined; + } >; 'page:techdocs/reader': ExtensionDefinition< { @@ -115,9 +121,11 @@ const _default: BackstagePlugin< } >, {}, - 'page', - undefined, - 'reader' + { + kind: 'page'; + namespace: undefined; + name: 'reader'; + } >; 'entity-content:techdocs': ExtensionDefinition< { @@ -159,9 +167,11 @@ const _default: BackstagePlugin< } >, {}, - 'entity-content', - undefined, - undefined + { + kind: 'entity-content'; + namespace: undefined; + name: undefined; + } >; } >; @@ -202,9 +212,11 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition< } >; }, - 'search-result-list-item', - undefined, - undefined + { + kind: 'search-result-list-item'; + namespace: undefined; + name: undefined; + } >; // (No @packageDocumentation comment for this package) diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index b79657cabc..f14c014a1e 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -33,9 +33,11 @@ const _default: BackstagePlugin< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; 'page:user-settings': ExtensionDefinition< { @@ -74,9 +76,11 @@ const _default: BackstagePlugin< } >; }, - 'page', - undefined, - undefined + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; @@ -96,9 +100,11 @@ export const settingsNavItem: ExtensionDefinition< {} >, {}, - 'nav-item', - undefined, - undefined + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; // @alpha (undocumented) From a0315a8a65753bee63cf086aef18b747f83adfaf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 09:47:57 +0200 Subject: [PATCH 312/393] docs/frontend-system: rename plugin extension blueprints to common extension blueprints + fix links Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-apps/01-index.md | 2 +- docs/frontend-system/building-plugins/01-index.md | 2 +- ...blueprints.md => 03-common-extension-blueprints.md} | 10 +++++----- docs/frontend-system/building-plugins/05-migrating.md | 2 +- microsite/sidebars.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename docs/frontend-system/building-plugins/{03-extension-blueprints.md => 03-common-extension-blueprints.md} (94%) diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index c6a83bb0b7..0654a9dd23 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -78,7 +78,7 @@ It is possible to enable, disable and configure extensions individually in the ` ### Customize or override built-in extensions -Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See the [extension blueprints](../building-plugins/03-extension-blueprints.md) section for a list of common extension kinds that are available for you to customize and extend your application. +Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See the [extension blueprints](../building-plugins/03-common-extension-blueprints.md) section for a list of common extension kinds that are available for you to customize and extend your application. ## Use code to customize the app at a more granular level diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 1925083ce3..c2a5de7280 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -229,4 +229,4 @@ export const examplePlugin = createFrontendPlugin({ The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). -For a more complete list of the different kinds of extensions that you can create for your plugin, see the [extension blueprints](./03-extension-blueprints.md) section. +For a more complete list of the different kinds of extensions that you can create for your plugin, see the [extension blueprints](./03-common-extension-blueprints.md) section. diff --git a/docs/frontend-system/building-plugins/03-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md similarity index 94% rename from docs/frontend-system/building-plugins/03-extension-blueprints.md rename to docs/frontend-system/building-plugins/03-common-extension-blueprints.md index 1607050fa6..5193fd85b1 100644 --- a/docs/frontend-system/building-plugins/03-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -1,7 +1,7 @@ --- -id: extension-blueprints -title: Frontend System Extension Blueprints -sidebar_label: Extension Blueprints +id: common-extension-blueprints +title: Common Extension Blueprints +sidebar_label: Common Extension Blueprints # prettier-ignore description: Extension blueprints provided by the frontend system and core features --- @@ -22,7 +22,7 @@ An API extension is used to add or override [Utility API factories](../utility-a Components extensions are used to override the component associated with a component reference throughout the app. This uses an extension creator function rather than a blueprint, but will likely be migrated to a blueprint in the future. -### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint) +### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint.md) Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. @@ -38,7 +38,7 @@ Sign-in page extension have a single purpose - to implement a custom sign-in pag Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. -### Icons - [Reference](../../reference/frontend-plugin-api.ndleblueprint.md) +### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md) Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app. diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index 9d978869e5..f0abc97c81 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -135,7 +135,7 @@ Then add the `fooPage` extension to the plugin: ## Migrating Components -The equivalent utility to replace components created with `createComponentExtension` depends on the context within which the component is used, typically indicated by the naming pattern of the export. Many of these can be migrated to one of the [existing blueprints](03-extension-blueprints.md), but in rare cases it may be necessary to use [`createExtension`](../architecture/20-extensions.md#creating-an-extension) directly. +The equivalent utility to replace components created with `createComponentExtension` depends on the context within which the component is used, typically indicated by the naming pattern of the export. Many of these can be migrated to one of the [existing blueprints](03-common-extension-blueprints.md), but in rare cases it may be necessary to use [`createExtension`](../architecture/20-extensions.md#creating-an-extension) directly. ## Migrating APIs diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 2accf2847b..1a95b5a492 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -469,7 +469,7 @@ "items": [ "frontend-system/building-plugins/index", "frontend-system/building-plugins/testing", - "frontend-system/building-plugins/extension-blueprints", + "frontend-system/building-plugins/common-extension-blueprints", "frontend-system/building-plugins/built-in-data-refs", "frontend-system/building-plugins/migrating" ] From 1cdc1172503a7332fad7e152961dcfdd9b94c4b5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 10:01:37 +0200 Subject: [PATCH 313/393] Update docs/frontend-system/building-plugins/02-testing.md Co-authored-by: Ben Lambert Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-plugins/02-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md index 956fdb8394..225c570fbd 100644 --- a/docs/frontend-system/building-plugins/02-testing.md +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -145,7 +145,7 @@ describe('Index page', async () => { }); ``` -When testing multiple extensions you may sometimes want to access the output of other extensions than the main test subject. You can use the `.query(ext)` method to query a different extension that has been added to the tester, either via its ID or passing the extension directly. +When testing multiple extensions you may sometimes want to access the output of other extensions than the main test subject. You can use the `.query(ext)` method to query a different extension that has been added to the tester, by passing the extension used with the `createExtensionTester(...).add(ext)` ### Setting configuration From d001a4208087cf7663d1cf09323bf7163bb1c526 Mon Sep 17 00:00:00 2001 From: Mikko Korhonen Date: Thu, 15 Aug 2024 16:45:37 +0300 Subject: [PATCH 314/393] fix: label related accessibility issues with FavoriteEntity Signed-off-by: Mikko Korhonen --- .changeset/twelve-peaches-develop.md | 5 + plugins/catalog-react/api-report.md | 1 + .../MockStarredEntitiesApi.ts | 4 + .../FavoriteEntity/FavoriteEntity.test.tsx | 108 ++++++++++++++++++ .../FavoriteEntity/FavoriteEntity.tsx | 22 ++-- .../TemplateCard/CardHeader.test.tsx | 2 +- 6 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 .changeset/twelve-peaches-develop.md create mode 100644 plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx diff --git a/.changeset/twelve-peaches-develop.md b/.changeset/twelve-peaches-develop.md new file mode 100644 index 0000000000..804c352ba4 --- /dev/null +++ b/.changeset/twelve-peaches-develop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fix label related accessibility issues with `FavorityEntity` diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 79aad53c33..b440701057 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -686,6 +686,7 @@ export function MockEntityListContextProvider< // @public export class MockStarredEntitiesApi implements StarredEntitiesApi { + constructor(opts?: { starredEntities?: string[] }); // (undocumented) starredEntitie$(): Observable>; // (undocumented) diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts index 9467153dd9..ed60051026 100644 --- a/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts @@ -29,6 +29,10 @@ export class MockStarredEntitiesApi implements StarredEntitiesApi { ZenObservable.SubscriptionObserver> >(); + constructor(opts: { starredEntities?: string[] } = {}) { + this.starredEntities = new Set(opts.starredEntities); + } + private readonly observable = new ObservableImpl>(subscriber => { subscriber.next(new Set(this.starredEntities)); diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx new file mode 100644 index 0000000000..a166d0a15d --- /dev/null +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { storageApiRef } from '@backstage/core-plugin-api'; + +import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../../apis'; +import { FavoriteEntity } from './FavoriteEntity'; +import { ComponentEntity } from '@backstage/catalog-model'; +import { + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'example', + }, + spec: { + type: 'service', + lifecycle: 'experimental', + owner: 'user:default/john.doe_example.com', + }, +}; + +const mockStorage = MockStorageApi.create(); + +describe('', () => { + it('should add to favorites', async () => { + await renderInTestApp( + + + , + ); + + const addToFavorite = screen.getByRole('button', { + name: 'Add to favorites', + }); + + // Should keep the label when hovering + await userEvent.hover(addToFavorite); + expect(addToFavorite).toBeInTheDocument(); + + await userEvent.click(addToFavorite); + expect( + screen.getByRole('button', { + name: 'Remove from favorites', + }), + ).toBeInTheDocument(); + }); + + it('should remove from favorites', async () => { + await renderInTestApp( + + + , + ); + + const removeFromFavorites = screen.getByRole('button', { + name: 'Remove from favorites', + }); + + // Should keep the label when hovering + await userEvent.hover(removeFromFavorites); + expect(removeFromFavorites).toBeInTheDocument(); + + await userEvent.click(removeFromFavorites); + + expect( + screen.getByRole('button', { + name: 'Add to favorites', + }), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index 8848857976..ab674ff98e 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import { withStyles } from '@material-ui/core/styles'; @@ -46,20 +46,24 @@ export const FavoriteEntity = (props: FavoriteEntityProps) => { props.entity, ); const { t } = useTranslationRef(catalogReactTranslationRef); + const title = isStarredEntity + ? t('favoriteEntity.removeFromFavorites') + : t('favoriteEntity.addToFavorites'); + + const id = `favorite-${stringifyEntityRef(props.entity).replace( + /[^a-zA-Z0-9-_]/g, + '-', + )}`; + return ( toggleStarredEntity()} > - + {isStarredEntity ? : } diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx index 90157c3226..58033b718c 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx @@ -119,7 +119,7 @@ describe('CardHeader', () => { , ); - const favorite = getByRole('button', { name: 'favorite' }); + const favorite = getByRole('button', { name: 'Add to favorites' }); await fireEvent.click(favorite); From 56be6445736df0629c856ab01e5c1c79d86ca959 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 11:46:53 +0200 Subject: [PATCH 315/393] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-apps/01-index.md | 2 +- docs/frontend-system/building-plugins/01-index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index 0654a9dd23..16aacc1185 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -78,7 +78,7 @@ It is possible to enable, disable and configure extensions individually in the ` ### Customize or override built-in extensions -Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See the [extension blueprints](../building-plugins/03-common-extension-blueprints.md) section for a list of common extension kinds that are available for you to customize and extend your application. +Previously you would customize the application routes, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want to allow the same thing to be achieved while writing less code and instead installing more extensions to customize your Backstage instance. See the [extension blueprints](../building-plugins/03-common-extension-blueprints.md) section for a list of common extension kinds that are available for you to customize and extend your application. ## Use code to customize the app at a more granular level diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index c2a5de7280..57a783d406 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -49,7 +49,7 @@ The plugin ID should be a lowercase dash-separated string, while the plugin inst The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/20-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. -To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` and `NavItemBlueprint`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. +To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` and `NavItemBlueprint`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for our page, allowing us to dynamically create URLs that link to our page. ```tsx title="in src/routes.ts" import { createRouteRef } from '@backstage/frontend-plugin-api'; From d42c283390433ae8a132d3d73a19d010d864cf6e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 11:47:45 +0200 Subject: [PATCH 316/393] Update docs/frontend-system/architecture/25-extension-overrides.md Signed-off-by: Patrik Oldsberg --- docs/frontend-system/architecture/25-extension-overrides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 1cfa71aa44..4bbb90edfb 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -81,7 +81,7 @@ Note the `yield*` expression, which forwards all values from the provided iterab ## Overriding declared outputs -When overriding an extension out can provide a new output declaration. This **replaces** any existing output declaration, which means that you want to forward any of the original output you will need to declare it again. The following example shows how to override an extension and replace the output declaration: +When overriding an extension you can provide a new output declaration. This **replaces** any existing output declaration, which means that you want to forward any of the original output you will need to declare it again. The following example shows how to override an extension and replace the output declaration: ```tsx // Original extension From c99c620809e9d75b59ba76732a258d84aa4c3c09 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Aug 2024 11:52:01 +0200 Subject: [PATCH 317/393] backend-plugin-api: Remove deprecated types Signed-off-by: Johan Haals --- .changeset/tame-doors-think.md | 9 +++ packages/backend-plugin-api/api-report.md | 28 ---------- packages/backend-plugin-api/src/deprecated.ts | 55 ------------------- packages/backend-plugin-api/src/index.ts | 1 - 4 files changed, 9 insertions(+), 84 deletions(-) create mode 100644 .changeset/tame-doors-think.md delete mode 100644 packages/backend-plugin-api/src/deprecated.ts diff --git a/.changeset/tame-doors-think.md b/.changeset/tame-doors-think.md new file mode 100644 index 0000000000..2e2ecabd35 --- /dev/null +++ b/.changeset/tame-doors-think.md @@ -0,0 +1,9 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING** Removed the following deprecated types: + +- `ServiceRefConfig` use `ServiceRefOptions` +- `RootServiceFactoryConfig` use `RootServiceFactoryOptions` +- `PluginServiceFactoryConfig` use `PluginServiceFactoryOptions` diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 319ddf0bc6..630f869be4 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -511,17 +511,6 @@ export interface PluginMetadataService { getId(): string; } -// @public @deprecated (undocumented) -export type PluginServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TContext, - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, -> = PluginServiceFactoryOptions; - // @public (undocumented) export interface PluginServiceFactoryOptions< TService, @@ -604,16 +593,6 @@ export interface RootLifecycleService extends LifecycleService {} // @public export interface RootLoggerService extends LoggerService {} -// @public @deprecated (undocumented) -export type RootServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, -> = RootServiceFactoryOptions; - // @public (undocumented) export interface RootServiceFactoryOptions< TService, // TODO(Rugvip): Can we forward the entire service ref type here instead of forwarding each type arg once the callback form is gone? @@ -755,13 +734,6 @@ export type ServiceRef< $$type: '@backstage/ServiceRef'; }; -// @public @deprecated (undocumented) -export type ServiceRefConfig< - TService, - TScope extends 'root' | 'plugin', - TInstances extends 'singleton' | 'multiton', -> = ServiceRefOptions; - // @public (undocumented) export interface ServiceRefOptions< TService, diff --git a/packages/backend-plugin-api/src/deprecated.ts b/packages/backend-plugin-api/src/deprecated.ts deleted file mode 100644 index 0d02f9ed7e..0000000000 --- a/packages/backend-plugin-api/src/deprecated.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - ServiceRef, - ServiceRefOptions, - RootServiceFactoryOptions, - PluginServiceFactoryOptions, -} from './services'; - -/** - * @public - * @deprecated Use {@link ServiceRefOptions} instead - */ -export type ServiceRefConfig< - TService, - TScope extends 'root' | 'plugin', - TInstances extends 'singleton' | 'multiton', -> = ServiceRefOptions; - -/** - * @public - * @deprecated Use {@link RootServiceFactoryOptions} instead - */ -export type RootServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TImpl extends TService, - TDeps extends { [name in string]: ServiceRef }, -> = RootServiceFactoryOptions; - -/** - * @public - * @deprecated Use {@link PluginServiceFactoryOptions} instead - */ -export type PluginServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TContext, - TImpl extends TService, - TDeps extends { [name in string]: ServiceRef }, -> = PluginServiceFactoryOptions; diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index eb554db25d..e6e57db1dc 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -24,4 +24,3 @@ export * from './services'; export type { BackendFeature, BackendFeatureCompat } from './types'; export * from './paths'; export * from './wiring'; -export * from './deprecated'; From c4b816958549c73012ceab83a2227e246e0bbc24 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Aug 2024 11:57:56 +0200 Subject: [PATCH 318/393] backend-plugin-api: Remove deprecated UrlReader types Signed-off-by: Johan Haals --- packages/backend-plugin-api/api-report.md | 28 ----------- .../services/definitions/UrlReaderService.ts | 47 ------------------- .../src/services/definitions/index.ts | 9 ---- 3 files changed, 84 deletions(-) diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 319ddf0bc6..0864064625 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -553,25 +553,6 @@ export function readSchedulerServiceTaskScheduleDefinitionFromConfig( config: Config, ): SchedulerServiceTaskScheduleDefinition; -// @public @deprecated (undocumented) -export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; - -// @public @deprecated (undocumented) -export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; - -// @public @deprecated (undocumented) -export type ReadTreeResponseDirOptions = - UrlReaderServiceReadTreeResponseDirOptions; - -// @public @deprecated (undocumented) -export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; - -// @public @deprecated (undocumented) -export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; - -// @public @deprecated (undocumented) -export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; - // @public export function resolvePackagePath(name: string, ...paths: string[]): string; @@ -707,15 +688,6 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig { timeout: string | HumanDuration; } -// @public @deprecated (undocumented) -export type SearchOptions = UrlReaderServiceSearchOptions; - -// @public @deprecated (undocumented) -export type SearchResponse = UrlReaderServiceSearchResponse; - -// @public @deprecated (undocumented) -export type SearchResponseFile = UrlReaderServiceSearchResponseFile; - // @public (undocumented) export interface ServiceFactory< TService = unknown, diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index 25b787107e..9b327cfbf0 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -368,50 +368,3 @@ export type UrlReaderServiceSearchResponseFile = { */ lastModifiedAt?: Date; }; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeOptions` instead - */ -export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponse` instead - */ -export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` instead - */ -export type ReadTreeResponseDirOptions = - UrlReaderServiceReadTreeResponseDirOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseFile` instead - */ -export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlResponse` instead - */ -export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlOptions` instead - */ -export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchOptions` instead - */ -export type SearchOptions = UrlReaderServiceSearchOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponse` instead - */ -export type SearchResponse = UrlReaderServiceSearchResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponseFile` instead - */ -export type SearchResponseFile = UrlReaderServiceSearchResponseFile; diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 5e30d444fd..194658dc0e 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -66,15 +66,6 @@ export type { } from './SchedulerService'; export type { TokenManagerService } from './TokenManagerService'; export type { - ReadTreeOptions, - ReadTreeResponse, - ReadTreeResponseDirOptions, - ReadTreeResponseFile, - ReadUrlResponse, - ReadUrlOptions, - SearchOptions, - SearchResponse, - SearchResponseFile, UrlReaderServiceReadTreeOptions, UrlReaderServiceReadTreeResponse, UrlReaderServiceReadTreeResponseDirOptions, From bcaf90bfb20a68a46ab14ded59effc92cc22c588 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 13:46:36 +0200 Subject: [PATCH 319/393] frontend-test-utils: avoid overriding test subject in extension tester unless rendering Signed-off-by: Patrik Oldsberg --- .../src/app/createExtensionTester.tsx | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index b3c1b385e3..1f901699b1 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -144,46 +144,7 @@ export class ExtensionTester { options?: { config?: TConfigInput }, ): ExtensionTester { const tester = new ExtensionTester(); - const internal = toInternalExtensionDefinition(subject); - - // attaching to app/routes to render as index route - if (internal.version === 'v1') { - tester.add( - createExtension({ - ...internal, - attachTo: { id: 'app/routes', input: 'routes' }, - output: { - ...internal.output, - path: coreExtensionData.routePath, - }, - factory: params => ({ - ...internal.factory(params as any), - path: '/', - }), - }), - options as TConfigInput & {}, - ); - } else if (internal.version === 'v2') { - tester.add( - createExtension({ - ...internal, - attachTo: { id: 'app/routes', input: 'routes' }, - output: internal.output.find( - ref => ref.id === coreExtensionData.routePath.id, - ) - ? internal.output - : [...internal.output, coreExtensionData.routePath], - factory: params => { - const parentOutput = Array.from( - internal.factory(params as any), - ).filter(val => val.id !== coreExtensionData.routePath.id); - - return [...parentOutput, coreExtensionData.routePath('/')]; - }, - }), - options as TConfigInput & {}, - ); - } + tester.add(subject, options as TConfigInput & {}); return tester; } @@ -288,11 +249,49 @@ export class ExtensionTester { ); } + const subjectInternal = toInternalExtensionDefinition(subject.definition); + let subjectOverride; + // attaching to app/routes to render as index route + if (subjectInternal.version === 'v1') { + subjectOverride = createExtension({ + ...subjectInternal, + attachTo: { id: 'app/routes', input: 'routes' }, + output: { + ...subjectInternal.output, + path: coreExtensionData.routePath, + }, + factory: params => ({ + ...subjectInternal.factory(params as any), + path: '/', + }), + }); + } else if (subjectInternal.version === 'v2') { + subjectOverride = createExtension({ + ...subjectInternal, + attachTo: { id: 'app/routes', input: 'routes' }, + output: subjectInternal.output.find( + ref => ref.id === coreExtensionData.routePath.id, + ) + ? subjectInternal.output + : [...subjectInternal.output, coreExtensionData.routePath], + factory: params => { + const parentOutput = Array.from( + subjectInternal.factory(params as any), + ).filter(val => val.id !== coreExtensionData.routePath.id); + + return [...parentOutput, coreExtensionData.routePath('/')]; + }, + }); + } else { + throw new Error('Unsupported extension version'); + } + const app = createSpecializedApp({ features: [ createExtensionOverrides({ extensions: [ - ...this.#extensions.map(extension => extension.definition), + subjectOverride, + ...this.#extensions.slice(1).map(extension => extension.definition), TestAppNavExtension, createRouterExtension({ namespace: 'test', From c32f62193152e3370a656af186daf2f215635c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 16 Aug 2024 14:08:19 +0200 Subject: [PATCH 320/393] declare data refs in the blueprints instead of extension creators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/frontend-plugin-api/api-report.md | 24 +++++++++---------- .../src/blueprints/ApiBlueprint.test.ts | 1 + .../src/blueprints/ApiBlueprint.ts | 13 ++++++---- .../AppRootElementBlueprint.test.tsx | 1 + .../src/blueprints/AppRootElementBlueprint.ts | 1 + .../AppRootWrapperBlueprint.test.tsx | 1 + .../blueprints/AppRootWrapperBlueprint.tsx | 13 ++++++---- .../src/blueprints/IconBundleBlueprint.ts | 1 + .../src/blueprints/NavItemBlueprint.ts | 16 +++++++++---- .../src/blueprints/NavLogoBlueprint.test.tsx | 1 + .../src/blueprints/NavLogoBlueprint.ts | 14 +++++++---- .../src/blueprints/RouterBlueprint.tsx | 14 +++++++---- .../src/blueprints/SignInPageBlueprint.tsx | 14 +++++++---- .../src/blueprints/ThemeBlueprint.ts | 15 ++++++------ .../src/blueprints/TranslationBlueprint.ts | 13 ++++++---- .../src/extensions/createApiExtension.ts | 21 ++++++++-------- .../createAppRootWrapperExtension.tsx | 16 ++++++++----- .../src/extensions/createNavItemExtension.tsx | 20 +++++++++------- .../src/extensions/createNavLogoExtension.tsx | 18 ++++++++------ .../src/extensions/createRouterExtension.tsx | 16 ++++++++----- .../extensions/createSignInPageExtension.tsx | 14 +++++++---- .../src/extensions/createThemeExtension.ts | 9 ++++--- .../extensions/createTranslationExtension.ts | 9 ++++--- 23 files changed, 158 insertions(+), 107 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index d19e60612d..a36c5e1046 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -488,9 +488,9 @@ export function createApiExtension< } >; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createApiExtension { - const // (undocumented) + const // @deprecated (undocumented) factoryDataRef: ConfigurableExtensionDataRef< AnyApiFactory, 'core.api.factory', @@ -546,9 +546,9 @@ export function createAppRootWrapperExtension< >; }): ExtensionDefinition; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createAppRootWrapperExtension { - const // (undocumented) + const // @deprecated (undocumented) componentDataRef: ConfigurableExtensionDataRef< React_2.ComponentType<{ children?: React_2.ReactNode; @@ -949,9 +949,9 @@ export function createNavItemExtension(options: { } >; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createNavItemExtension { - const // (undocumented) + const // @deprecated (undocumented) targetDataRef: ConfigurableExtensionDataRef< { title: string; @@ -981,9 +981,9 @@ export function createNavLogoExtension(options: { } >; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createNavLogoExtension { - const // (undocumented) + const // @deprecated (undocumented) logoElementsDataRef: ConfigurableExtensionDataRef< { logoIcon?: JSX.Element | undefined; @@ -1070,9 +1070,9 @@ export function createRouterExtension< >; }): ExtensionDefinition; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createRouterExtension { - const // (undocumented) + const // @deprecated (undocumented) componentDataRef: ConfigurableExtensionDataRef< React_2.ComponentType<{ children?: React_2.ReactNode; @@ -1107,9 +1107,9 @@ export function createSignInPageExtension< }) => Promise>; }): ExtensionDefinition; -// @public (undocumented) +// @public @deprecated (undocumented) export namespace createSignInPageExtension { - const // (undocumented) + const // @deprecated (undocumented) componentDataRef: ConfigurableExtensionDataRef< React_2.ComponentType, 'core.sign-in-page.component', diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 3b865a78bb..c9dd7811b6 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts index b5ba9e05f6..73eec80b81 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '../wiring'; -import { createApiExtension } from '../extensions/createApiExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; import { AnyApiFactory } from '@backstage/core-plugin-api'; +const factoryDataRef = createExtensionDataRef().with({ + id: 'core.api.factory', +}); + /** * Creates utility API extensions. * @@ -26,11 +29,11 @@ import { AnyApiFactory } from '@backstage/core-plugin-api'; export const ApiBlueprint = createExtensionBlueprint({ kind: 'api', attachTo: { id: 'app', input: 'apis' }, - output: [createApiExtension.factoryDataRef], + output: [factoryDataRef], dataRefs: { - factory: createApiExtension.factoryDataRef, + factory: factoryDataRef, }, *factory(params: { factory: AnyApiFactory }) { - yield createApiExtension.factoryDataRef(params.factory); + yield factoryDataRef(params.factory); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx index 3fb8d7b37a..e76bed8155 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { AppRootElementBlueprint } from './AppRootElementBlueprint'; diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts index bc6304923a..c823fa56cd 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { coreExtensionData, createExtensionBlueprint } from '../wiring'; /** diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx index f7feab1d46..5cbb96fbbf 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index c54686f54f..da11bd8cb0 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -16,8 +16,11 @@ import React from 'react'; import { ComponentType, PropsWithChildren } from 'react'; -import { createExtensionBlueprint } from '../wiring'; -import { createAppRootWrapperExtension } from '../extensions/createAppRootWrapperExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const componentDataRef = createExtensionDataRef< + ComponentType> +>().with({ id: 'app.root.wrapper' }); /** * Creates a extensions that render a React wrapper at the app root, enclosing @@ -29,9 +32,9 @@ import { createAppRootWrapperExtension } from '../extensions/createAppRootWrappe export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', attachTo: { id: 'app/root', input: 'wrappers' }, - output: [createAppRootWrapperExtension.componentDataRef], + output: [componentDataRef], dataRefs: { - component: createAppRootWrapperExtension.componentDataRef, + component: componentDataRef, }, *factory(params: { Component: ComponentType> }) { // todo(blam): not sure that this wrapping is even necessary anymore. @@ -39,6 +42,6 @@ export const AppRootWrapperBlueprint = createExtensionBlueprint({ return {props.children}; }; - yield createAppRootWrapperExtension.componentDataRef(Component); + yield componentDataRef(Component); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts index deeabdb44c..52d6e2482d 100644 --- a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { IconComponent } from '../icons'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 20f57421c7..197f72da73 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -16,8 +16,14 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { RouteRef } from '../routing'; -import { createExtensionBlueprint } from '../wiring'; -import { createNavItemExtension } from '../extensions/createNavItemExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +// TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef +const targetDataRef = createExtensionDataRef<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; +}>().with({ id: 'core.nav-item.target' }); /** * Creates extensions that make up the items of the nav bar. @@ -27,9 +33,9 @@ import { createNavItemExtension } from '../extensions/createNavItemExtension'; export const NavItemBlueprint = createExtensionBlueprint({ kind: 'nav-item', attachTo: { id: 'app/nav', input: 'items' }, - output: [createNavItemExtension.targetDataRef], + output: [targetDataRef], dataRefs: { - target: createNavItemExtension.targetDataRef, + target: targetDataRef, }, factory: ( { @@ -43,7 +49,7 @@ export const NavItemBlueprint = createExtensionBlueprint({ }, { config }, ) => [ - createNavItemExtension.targetDataRef({ + targetDataRef({ title: config.title ?? title, icon, routeRef, diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx index b976196080..621dca72ba 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { NavLogoBlueprint } from './NavLogoBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts index 8066646ca3..4a37859975 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '../wiring'; -import { createNavLogoExtension } from '../extensions/createNavLogoExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const logoElementsDataRef = createExtensionDataRef<{ + logoIcon?: JSX.Element; + logoFull?: JSX.Element; +}>().with({ id: 'core.nav-logo.logo-elements' }); /** * Creates an extension that replaces the logo in the nav bar with your own. @@ -25,9 +29,9 @@ import { createNavLogoExtension } from '../extensions/createNavLogoExtension'; export const NavLogoBlueprint = createExtensionBlueprint({ kind: 'nav-logo', attachTo: { id: 'app/nav', input: 'logos' }, - output: [createNavLogoExtension.logoElementsDataRef], + output: [logoElementsDataRef], dataRefs: { - logoElements: createNavLogoExtension.logoElementsDataRef, + logoElements: logoElementsDataRef, }, *factory({ logoIcon, @@ -36,7 +40,7 @@ export const NavLogoBlueprint = createExtensionBlueprint({ logoIcon: JSX.Element; logoFull: JSX.Element; }) { - yield createNavLogoExtension.logoElementsDataRef({ + yield logoElementsDataRef({ logoIcon, logoFull, }); diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx index 24ee805659..503c9da0e8 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { ComponentType, PropsWithChildren } from 'react'; -import { createExtensionBlueprint } from '../wiring'; -import { createRouterExtension } from '../extensions/createRouterExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const componentDataRef = createExtensionDataRef< + ComponentType> +>().with({ id: 'app.router.wrapper' }); /** @public */ export const RouterBlueprint = createExtensionBlueprint({ kind: 'app-router-component', attachTo: { id: 'app/root', input: 'router' }, - output: [createRouterExtension.componentDataRef], + output: [componentDataRef], dataRefs: { - component: createRouterExtension.componentDataRef, + component: componentDataRef, }, *factory({ Component }: { Component: ComponentType> }) { - yield createRouterExtension.componentDataRef(Component); + yield componentDataRef(Component); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index f87ae91452..561781c49f 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -13,12 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React, { ComponentType, lazy } from 'react'; -import { createExtensionBlueprint } from '../wiring'; -import { createSignInPageExtension } from '../extensions/createSignInPageExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; import { SignInPageProps } from '@backstage/core-plugin-api'; import { ExtensionBoundary } from '../components'; +const componentDataRef = createExtensionDataRef< + ComponentType +>().with({ id: 'core.sign-in-page.component' }); + /** * Creates an extension that replaces the sign in page. * @@ -27,9 +31,9 @@ import { ExtensionBoundary } from '../components'; export const SignInPageBlueprint = createExtensionBlueprint({ kind: 'sign-in-page', attachTo: { id: 'app/root', input: 'signInPage' }, - output: [createSignInPageExtension.componentDataRef], + output: [componentDataRef], dataRefs: { - component: createSignInPageExtension.componentDataRef, + component: componentDataRef, }, *factory( { @@ -43,7 +47,7 @@ export const SignInPageBlueprint = createExtensionBlueprint({ loader().then(component => ({ default: component })), ); - yield createSignInPageExtension.componentDataRef(props => ( + yield componentDataRef(props => ( diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index 686c498c06..a4f550595f 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -15,8 +15,11 @@ */ import { AppTheme } from '@backstage/core-plugin-api'; -import { createExtensionBlueprint } from '../wiring'; -import { createThemeExtension } from '../extensions/createThemeExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const themeDataRef = createExtensionDataRef().with({ + id: 'core.theme.theme', +}); /** * Creates an extension that adds/replaces an app theme. @@ -27,11 +30,9 @@ export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', namespace: 'app', attachTo: { id: 'app', input: 'themes' }, - output: [createThemeExtension.themeDataRef], + output: [themeDataRef], dataRefs: { - theme: createThemeExtension.themeDataRef, + theme: themeDataRef, }, - factory: ({ theme }: { theme: AppTheme }) => [ - createThemeExtension.themeDataRef(theme), - ], + factory: ({ theme }: { theme: AppTheme }) => [themeDataRef(theme)], }); diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts index bdb36e2191..7b01d0b28f 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '../wiring'; -import { createTranslationExtension } from '../extensions/createTranslationExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; import { TranslationMessages, TranslationResource } from '../translation'; +const translationDataRef = createExtensionDataRef< + TranslationResource | TranslationMessages +>().with({ id: 'core.translation.translation' }); + /** * Creates an extension that adds translations to your app. * @@ -26,13 +29,13 @@ import { TranslationMessages, TranslationResource } from '../translation'; export const TranslationBlueprint = createExtensionBlueprint({ kind: 'translation', attachTo: { id: 'app', input: 'translations' }, - output: [createTranslationExtension.translationDataRef], + output: [translationDataRef], dataRefs: { - translation: createTranslationExtension.translationDataRef, + translation: translationDataRef, }, factory: ({ resource, }: { resource: TranslationResource | TranslationMessages; - }) => [createTranslationExtension.translationDataRef(resource)], + }) => [translationDataRef(resource)], }); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index b1db2a0106..bb854d202f 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -16,13 +16,10 @@ import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; import { PortableSchema } from '../schema'; -import { - ResolvedExtensionInputs, - createExtension, - createExtensionDataRef, -} from '../wiring'; +import { ResolvedExtensionInputs, createExtension } from '../wiring'; import { AnyExtensionInputMap } from '../wiring/createExtension'; import { Expand } from '../types'; +import { ApiBlueprint } from '../blueprints/ApiBlueprint'; /** * @public @@ -62,7 +59,7 @@ export function createApiExtension< inputs: extensionInputs, configSchema, output: { - api: createApiExtension.factoryDataRef, + api: ApiBlueprint.dataRefs.factory, }, factory({ config, inputs }) { if (typeof factory === 'function') { @@ -73,9 +70,13 @@ export function createApiExtension< }); } -/** @public */ +/** + * @public + * @deprecated Use {@link ApiBlueprint} instead. + */ export namespace createApiExtension { - export const factoryDataRef = createExtensionDataRef().with({ - id: 'core.api.factory', - }); + /** + * @deprecated Use {@link ApiBlueprint} instead. + */ + export const factoryDataRef = ApiBlueprint.dataRefs.factory; } diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx index 4f70f29470..f055618ffd 100644 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx @@ -22,8 +22,8 @@ import { ResolvedExtensionInputs, createExtension, } from '../wiring/createExtension'; -import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; import { Expand } from '../types'; +import { AppRootWrapperBlueprint } from '../blueprints/AppRootWrapperBlueprint'; /** * Creates an extension that renders a React wrapper at the app root, enclosing @@ -59,7 +59,7 @@ export function createAppRootWrapperExtension< disabled: options.disabled, inputs: options.inputs, output: { - component: createAppRootWrapperExtension.componentDataRef, + component: AppRootWrapperBlueprint.dataRefs.component, }, factory({ inputs, config }) { const Component = (props: PropsWithChildren<{}>) => { @@ -76,9 +76,13 @@ export function createAppRootWrapperExtension< }); } -/** @public */ +/** + * @public + * @deprecated Use {@link AppRootWrapperBlueprint} instead. + */ export namespace createAppRootWrapperExtension { - export const componentDataRef = createExtensionDataRef< - ComponentType> - >().with({ id: 'app.root.wrapper' }); + /** + * @deprecated Use {@link AppRootWrapperBlueprint} instead. + */ + export const componentDataRef = AppRootWrapperBlueprint.dataRefs.component; } diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx index d694440427..f6da531776 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx @@ -16,8 +16,9 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { createExtension, createExtensionDataRef } from '../wiring'; +import { createExtension } from '../wiring'; import { RouteRef } from '../routing'; +import { NavItemBlueprint } from '../blueprints/NavItemBlueprint'; /** * Helper for creating extensions for a nav item. @@ -44,7 +45,7 @@ export function createNavItemExtension(options: { }), ), output: { - navTarget: createNavItemExtension.targetDataRef, + navTarget: NavItemBlueprint.dataRefs.target, }, factory: ({ config }) => ({ navTarget: { @@ -56,12 +57,13 @@ export function createNavItemExtension(options: { }); } -/** @public */ +/** + * @public + * @deprecated Use {@link NavItemBlueprint} instead. + */ export namespace createNavItemExtension { - // TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef - export const targetDataRef = createExtensionDataRef<{ - title: string; - icon: IconComponent; - routeRef: RouteRef; - }>().with({ id: 'core.nav-item.target' }); + /** + * @deprecated Use {@link NavItemBlueprint} instead. + */ + export const targetDataRef = NavItemBlueprint.dataRefs.target; } diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx index 02c32e7f34..449bddb342 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { createExtension, createExtensionDataRef } from '../wiring'; +import { createExtension } from '../wiring'; +import { NavLogoBlueprint } from '../blueprints/NavLogoBlueprint'; /** * Helper for creating extensions for a nav logos. @@ -35,7 +36,7 @@ export function createNavLogoExtension(options: { namespace: options?.namespace, attachTo: { id: 'app/nav', input: 'logos' }, output: { - logos: createNavLogoExtension.logoElementsDataRef, + logos: NavLogoBlueprint.dataRefs.logoElements, }, factory: () => { return { @@ -48,10 +49,13 @@ export function createNavLogoExtension(options: { }); } -/** @public */ +/** + * @public + * @deprecated Use {@link NavLogoBlueprint} instead. + */ export namespace createNavLogoExtension { - export const logoElementsDataRef = createExtensionDataRef<{ - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }>().with({ id: 'core.nav-logo.logo-elements' }); + /** + * @deprecated Use {@link NavLogoBlueprint} instead. + */ + export const logoElementsDataRef = NavLogoBlueprint.dataRefs.logoElements; } diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx index 943711f5e0..258940c694 100644 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx @@ -22,8 +22,8 @@ import { ResolvedExtensionInputs, createExtension, } from '../wiring/createExtension'; -import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; import { Expand } from '../types'; +import { RouterBlueprint } from '../blueprints/RouterBlueprint'; /** * Creates an extension that replaces the router implementation at the app root. @@ -59,7 +59,7 @@ export function createRouterExtension< disabled: options.disabled, inputs: options.inputs, output: { - component: createRouterExtension.componentDataRef, + component: RouterBlueprint.dataRefs.component, }, factory({ inputs, config }) { const Component = (props: PropsWithChildren<{}>) => { @@ -76,9 +76,13 @@ export function createRouterExtension< }); } -/** @public */ +/** + * @public + * @deprecated Use {@link RouterBlueprint} instead. + */ export namespace createRouterExtension { - export const componentDataRef = createExtensionDataRef< - ComponentType> - >().with({ id: 'app.router.wrapper' }); + /** + * @deprecated Use {@link RouterBlueprint} instead. + */ + export const componentDataRef = RouterBlueprint.dataRefs.component; } diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx index 3f41202f11..9c663b72f5 100644 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx @@ -21,11 +21,11 @@ import { createExtension, ResolvedExtensionInputs, AnyExtensionInputMap, - createExtensionDataRef, ExtensionDefinition, } from '../wiring'; import { Expand } from '../types'; import { SignInPageProps } from '@backstage/core-plugin-api'; +import { SignInPageBlueprint } from '../blueprints'; /** * @@ -76,9 +76,13 @@ export function createSignInPageExtension< }); } -/** @public */ +/** + * @public + * @deprecated Use {@link SignInPageBlueprint} instead. + */ export namespace createSignInPageExtension { - export const componentDataRef = createExtensionDataRef< - ComponentType - >().with({ id: 'core.sign-in-page.component' }); + /** + * @deprecated Use {@link SignInPageBlueprint} instead. + */ + export const componentDataRef = SignInPageBlueprint.dataRefs.component; } diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts index e523fd9c05..2662ebb39c 100644 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { createExtension, createExtensionDataRef } from '../wiring'; +import { ThemeBlueprint } from '../blueprints/ThemeBlueprint'; +import { createExtension } from '../wiring'; import { AppTheme } from '@backstage/core-plugin-api'; /** @@ -28,7 +29,7 @@ export function createThemeExtension(theme: AppTheme) { name: theme.id, attachTo: { id: 'app', input: 'themes' }, output: { - theme: createThemeExtension.themeDataRef, + theme: ThemeBlueprint.dataRefs.theme, }, factory: () => ({ theme }), }); @@ -39,7 +40,5 @@ export function createThemeExtension(theme: AppTheme) { * @deprecated Use {@link ThemeBlueprint} instead. */ export namespace createThemeExtension { - export const themeDataRef = createExtensionDataRef().with({ - id: 'core.theme.theme', - }); + export const themeDataRef = ThemeBlueprint.dataRefs.theme; } diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts index c4d2f6037d..8a094cd593 100644 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts @@ -14,8 +14,9 @@ * limitations under the License. */ +import { TranslationBlueprint } from '../blueprints/TranslationBlueprint'; import { TranslationMessages, TranslationResource } from '../translation'; -import { createExtension, createExtensionDataRef } from '../wiring'; +import { createExtension } from '../wiring'; /** * @public @@ -31,7 +32,7 @@ export function createTranslationExtension(options: { name: options.name, attachTo: { id: 'app', input: 'translations' }, output: { - resource: createTranslationExtension.translationDataRef, + resource: TranslationBlueprint.dataRefs.translation, }, factory: () => ({ resource: options.resource }), }); @@ -42,7 +43,5 @@ export function createTranslationExtension(options: { * @deprecated Use {@link TranslationBlueprint} instead. */ export namespace createTranslationExtension { - export const translationDataRef = createExtensionDataRef< - TranslationResource | TranslationMessages - >().with({ id: 'core.translation.translation' }); + export const translationDataRef = TranslationBlueprint.dataRefs.translation; } From 813cac4bf276c976f100bec2c9d0faf79f0cbe28 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 15 Aug 2024 15:57:20 +0200 Subject: [PATCH 321/393] introduce `ExtensionBoundary.lazy` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: blam Co-authored-by: Fredrik Adelöw --- .changeset/good-balloons-fix.md | 5 +++++ packages/frontend-plugin-api/api-report.md | 11 ++++++++++- .../src/blueprints/PageBlueprint.tsx | 12 ++---------- .../src/components/ExtensionBoundary.tsx | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 .changeset/good-balloons-fix.md diff --git a/.changeset/good-balloons-fix.md b/.changeset/good-balloons-fix.md new file mode 100644 index 0000000000..be3d8e5785 --- /dev/null +++ b/.changeset/good-balloons-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Add an `ExtensionBoundary.lazy` function to create properly wrapped lazy-loading enabled elements, suitable for use with `coreExtensionData.reactElement`. The page blueprint now automatically leverages this. diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index a36c5e1046..a6ca3f3aff 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -1362,6 +1362,15 @@ export function ExtensionBoundary( props: ExtensionBoundaryProps, ): React_2.JSX.Element; +// @public (undocumented) +export namespace ExtensionBoundary { + // (undocumented) + export function lazy( + appNode: AppNode, + lazyElement: () => Promise, + ): JSX.Element; +} + // @public (undocumented) export interface ExtensionBoundaryProps { // (undocumented) @@ -1840,7 +1849,7 @@ export const PageBlueprint: ExtensionBlueprint< loader: () => Promise; routeRef?: RouteRef | undefined; }, - | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< RouteRef, diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index b224ea491e..bdfb0141c8 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { lazy } from 'react'; + import { RouteRef } from '../routing'; import { coreExtensionData, createExtensionBlueprint } from '../wiring'; import { ExtensionBoundary } from '../components'; @@ -48,16 +48,8 @@ export const PageBlueprint = createExtensionBlueprint({ }, { config, node }, ) { - const ExtensionComponent = lazy(() => - loader().then(element => ({ default: () => element })), - ); - yield coreExtensionData.routePath(config.path ?? defaultPath); - yield coreExtensionData.reactElement( - - - , - ); + yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); if (routeRef) { yield coreExtensionData.routeRef(routeRef); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index b6269484b0..176dfd4c3c 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -19,6 +19,7 @@ import React, { ReactNode, Suspense, useEffect, + lazy as reactLazy, } from 'react'; import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; import { ErrorBoundary } from './ErrorBoundary'; @@ -90,3 +91,20 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { ); } + +/** @public */ +export namespace ExtensionBoundary { + export function lazy( + appNode: AppNode, + lazyElement: () => Promise, + ): JSX.Element { + const ExtensionComponent = reactLazy(() => + lazyElement().then(element => ({ default: () => element })), + ); + return ( + + + + ); + } +} From ebe7c4df7ca64a7fb6efed074e2e1ad2bb2d6f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 16 Aug 2024 14:27:15 +0200 Subject: [PATCH 322/393] apply in more places MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/blueprints/PageBlueprint.tsx | 1 - plugins/catalog-react/api-report-alpha.md | 6 +++--- ...ntityCardBlueprint.tsx => EntityCardBlueprint.ts} | 12 ++---------- ...ontentBlueprint.tsx => EntityContentBlueprint.ts} | 12 ++---------- plugins/catalog/api-report-alpha.md | 3 +-- ...FilterBlueprint.tsx => CatalogFilterBlueprint.ts} | 8 +------- 6 files changed, 9 insertions(+), 33 deletions(-) rename plugins/catalog-react/src/alpha/blueprints/{EntityCardBlueprint.tsx => EntityCardBlueprint.ts} (87%) rename plugins/catalog-react/src/alpha/blueprints/{EntityContentBlueprint.tsx => EntityContentBlueprint.ts} (90%) rename plugins/catalog/src/alpha/blueprints/{CatalogFilterBlueprint.tsx => CatalogFilterBlueprint.ts} (81%) diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index bdfb0141c8..303e99daaa 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -50,7 +50,6 @@ export const PageBlueprint = createExtensionBlueprint({ ) { yield coreExtensionData.routePath(config.path ?? defaultPath); yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); - if (routeRef) { yield coreExtensionData.routeRef(routeRef); } diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index 6f801ed45f..f5828993e7 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -11,8 +11,8 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; import { PortableSchema } from '@backstage/frontend-plugin-api'; -import { default as React_2 } from 'react'; import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -182,7 +182,7 @@ export const EntityCardBlueprint: ExtensionBlueprint< loader: () => Promise; filter?: string | ((entity: Entity) => boolean) | undefined; }, - | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', @@ -232,7 +232,7 @@ export const EntityContentBlueprint: ExtensionBlueprint< routeRef?: RouteRef | undefined; filter?: string | ((entity: Entity) => boolean) | undefined; }, - | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< RouteRef, diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts similarity index 87% rename from plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx rename to plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts index c3153b6059..d13fd2f2c0 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { lazy } from 'react'; + import { ExtensionBoundary, coreExtensionData, @@ -54,15 +54,7 @@ export const EntityCardBlueprint = createExtensionBlueprint({ }, { node, config }, ) { - const ExtensionComponent = lazy(() => - loader().then(element => ({ default: () => element })), - ); - - yield coreExtensionData.reactElement( - - - , - ); + yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); if (config.filter) { yield catalogExtensionData.entityFilterExpression(config.filter); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts similarity index 90% rename from plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx rename to plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts index 7c7a6c43b7..36bca99ecc 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { lazy } from 'react'; + import { coreExtensionData, createExtensionBlueprint, @@ -70,15 +70,7 @@ export const EntityContentBlueprint = createExtensionBlueprint({ const path = config.path ?? defaultPath; const title = config.title ?? defaultTitle; - const ExtensionComponent = lazy(() => - loader().then(element => ({ default: () => element })), - ); - - yield coreExtensionData.reactElement( - - - , - ); + yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); yield coreExtensionData.routePath(path); diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 560f179efa..806fad5754 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -19,7 +19,6 @@ import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { PortableSchema } from '@backstage/frontend-plugin-api'; -import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; @@ -35,7 +34,7 @@ export const CatalogFilterBlueprint: ExtensionBlueprint< { loader: () => Promise; }, - ConfigurableExtensionDataRef, + ConfigurableExtensionDataRef, {}, {}, {}, diff --git a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.ts similarity index 81% rename from plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx rename to plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.ts index e8d6df55d2..a1063b2526 100644 --- a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.tsx +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import React, { lazy } from 'react'; import { ExtensionBoundary, coreExtensionData, @@ -30,14 +29,9 @@ export const CatalogFilterBlueprint = createExtensionBlueprint({ attachTo: { id: 'page:catalog', input: 'filters' }, output: [coreExtensionData.reactElement], factory(params: { loader: () => Promise }, { node }) { - const ExtensionComponent = lazy(() => - params.loader().then(element => ({ default: () => element })), - ); return [ coreExtensionData.reactElement( - - - , + ExtensionBoundary.lazy(node, params.loader), ), ]; }, From 8925f18f7c93a6725010498e4412545a0e189e2e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 4 Aug 2024 17:24:10 +0200 Subject: [PATCH 323/393] core-compat-api: initial stab at plugin conversion Signed-off-by: Patrik Oldsberg --- packages/core-compat-api/package.json | 3 +- .../src/convertLegacyPlugin.test.tsx | 0 .../src/convertLegacyPlugin.tsx | 85 +++++++++++++++++++ yarn.lock | 1 + 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 packages/core-compat-api/src/convertLegacyPlugin.test.tsx create mode 100644 packages/core-compat-api/src/convertLegacyPlugin.tsx diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 560a547af5..9ebc1a0185 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -34,7 +34,8 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/version-bridge": "workspace:^", - "@types/react": "^16.13.1 || ^17.0.0" + "@types/react": "^16.13.1 || ^17.0.0", + "lodash": "^4.17.21" }, "devDependencies": { "@backstage-community/plugin-puppetdb": "^0.1.18", diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core-compat-api/src/convertLegacyPlugin.tsx b/packages/core-compat-api/src/convertLegacyPlugin.tsx new file mode 100644 index 0000000000..052888d53c --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPlugin.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BackstagePlugin as LegacyBackstagePlugin, + getComponentData, + RouteRef as LegacyRouteRef, +} from '@backstage/core-plugin-api'; +import { + ExtensionDefinition, + BackstagePlugin as NewBackstagePlugin, + createPageExtension, + createPlugin, +} from '@backstage/frontend-plugin-api'; +import kebabCase from 'lodash/kebabCase'; +import { + convertLegacyRouteRef, + convertLegacyRouteRefs, +} from './convertLegacyRouteRef'; +import { ComponentType } from 'react'; +import React from 'react'; +import { compatWrapper } from './compatWrapper'; + +/** @internal */ +export function convertLegacyExtension( + LegacyExtension: ComponentType<{}>, + legacyPlugin: LegacyBackstagePlugin, +): ExtensionDefinition { + const element = ; + + const plugin = getComponentData( + element, + 'core.plugin', + ); + if (legacyPlugin !== plugin) { + throw new Error( + `The extension does not belong to the same plugin, got ${plugin?.getId()}`, + ); + } + + const name = getComponentData(element, 'core.extensionName'); + if (!name) { + throw new Error('Extension has no name'); + } + + const mountPoint = getComponentData( + element, + 'core.mountPoint', + ); + + if (name.endsWith('Page')) { + return createPageExtension({ + defaultPath: kebabCase(name.slice(0, -'Page'.length)), + routeRef: mountPoint && convertLegacyRouteRef(mountPoint), + loader: () => Promise.resolve(compatWrapper(element)), + }); + } +} + +/** @public */ +export function convertLegacyPlugin( + legacyPlugin: LegacyBackstagePlugin, + options: { extensions: ExtensionDefinition[] }, +): NewBackstagePlugin { + return createPlugin({ + id: legacyPlugin.getId(), + featureFlags: [...legacyPlugin.getFeatureFlags()], + routes: convertLegacyRouteRefs(legacyPlugin.routes ?? {}), + externalRoutes: convertLegacyRouteRefs(legacyPlugin.externalRoutes ?? {}), + extensions: options.extensions, + }); +} diff --git a/yarn.lock b/yarn.lock index 301d7de22f..82b65687e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4202,6 +4202,7 @@ __metadata: "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^15.0.0 "@types/react": ^16.13.1 || ^17.0.0 + lodash: ^4.17.21 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 From 71fb0269e3454dff644b78ff4556dbf73eee5b5f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 13:01:27 +0200 Subject: [PATCH 324/393] core-compat-api: spit out explicit legacy extension converter Signed-off-by: Patrik Oldsberg --- ...gin.tsx => convertLegacyPageExtension.tsx} | 63 +++++++------------ .../src/convertLegacyPlugin.ts | 37 +++++++++++ 2 files changed, 58 insertions(+), 42 deletions(-) rename packages/core-compat-api/src/{convertLegacyPlugin.tsx => convertLegacyPageExtension.tsx} (50%) create mode 100644 packages/core-compat-api/src/convertLegacyPlugin.ts diff --git a/packages/core-compat-api/src/convertLegacyPlugin.tsx b/packages/core-compat-api/src/convertLegacyPageExtension.tsx similarity index 50% rename from packages/core-compat-api/src/convertLegacyPlugin.tsx rename to packages/core-compat-api/src/convertLegacyPageExtension.tsx index 052888d53c..2ff5e0c574 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.tsx +++ b/packages/core-compat-api/src/convertLegacyPageExtension.tsx @@ -15,44 +15,31 @@ */ import { - BackstagePlugin as LegacyBackstagePlugin, getComponentData, RouteRef as LegacyRouteRef, } from '@backstage/core-plugin-api'; import { ExtensionDefinition, - BackstagePlugin as NewBackstagePlugin, - createPageExtension, - createPlugin, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import kebabCase from 'lodash/kebabCase'; -import { - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from './convertLegacyRouteRef'; +import { convertLegacyRouteRef } from './convertLegacyRouteRef'; import { ComponentType } from 'react'; import React from 'react'; import { compatWrapper } from './compatWrapper'; -/** @internal */ -export function convertLegacyExtension( +/** @public */ +export function convertLegacyPageExtension( LegacyExtension: ComponentType<{}>, - legacyPlugin: LegacyBackstagePlugin, -): ExtensionDefinition { + overrides?: { + name?: string; + defaultPath?: string; + }, +): ExtensionDefinition { const element = ; - const plugin = getComponentData( - element, - 'core.plugin', - ); - if (legacyPlugin !== plugin) { - throw new Error( - `The extension does not belong to the same plugin, got ${plugin?.getId()}`, - ); - } - - const name = getComponentData(element, 'core.extensionName'); - if (!name) { + const extName = getComponentData(element, 'core.extensionName'); + if (!extName) { throw new Error('Extension has no name'); } @@ -61,25 +48,17 @@ export function convertLegacyExtension( 'core.mountPoint', ); - if (name.endsWith('Page')) { - return createPageExtension({ - defaultPath: kebabCase(name.slice(0, -'Page'.length)), + const name = extName.endsWith('Page') + ? extName.slice(0, -'Page'.length) + : extName; + const kebabName = kebabCase(name); + + return PageBlueprint.make({ + name: overrides?.name ?? kebabName, + params: { + defaultPath: overrides?.defaultPath ?? `/${kebabName}`, routeRef: mountPoint && convertLegacyRouteRef(mountPoint), loader: () => Promise.resolve(compatWrapper(element)), - }); - } -} - -/** @public */ -export function convertLegacyPlugin( - legacyPlugin: LegacyBackstagePlugin, - options: { extensions: ExtensionDefinition[] }, -): NewBackstagePlugin { - return createPlugin({ - id: legacyPlugin.getId(), - featureFlags: [...legacyPlugin.getFeatureFlags()], - routes: convertLegacyRouteRefs(legacyPlugin.routes ?? {}), - externalRoutes: convertLegacyRouteRefs(legacyPlugin.externalRoutes ?? {}), - extensions: options.extensions, + }, }); } diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts new file mode 100644 index 0000000000..05beb4e469 --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPlugin.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BackstagePlugin as LegacyBackstagePlugin } from '@backstage/core-plugin-api'; +import { + ExtensionDefinition, + BackstagePlugin as NewBackstagePlugin, + createFrontendPlugin, +} from '@backstage/frontend-plugin-api'; +import { convertLegacyRouteRefs } from './convertLegacyRouteRef'; + +/** @public */ +export function convertLegacyPlugin( + legacyPlugin: LegacyBackstagePlugin, + options: { extensions: ExtensionDefinition[] }, +): NewBackstagePlugin { + return createFrontendPlugin({ + id: legacyPlugin.getId(), + featureFlags: [...legacyPlugin.getFeatureFlags()], + routes: convertLegacyRouteRefs(legacyPlugin.routes ?? {}), + externalRoutes: convertLegacyRouteRefs(legacyPlugin.externalRoutes ?? {}), + extensions: options.extensions, + }); +} From 6f23e5c24ef266655868db80029f2f70c1ba52a3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 13:15:54 +0200 Subject: [PATCH 325/393] core-compat-api: test + support APIs for convertLegacyPlugin Signed-off-by: Patrik Oldsberg --- .../src/convertLegacyPlugin.test.tsx | 91 +++++++++++++++++++ .../src/convertLegacyPlugin.ts | 6 +- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index e69de29bb2..4e17c1d61b 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createExternalRouteRef as createLegacyExternalRouteRef, + createApiFactory, + createApiRef, +} from '@backstage/core-plugin-api'; +import { convertLegacyPlugin } from './convertLegacyPlugin'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; + +describe('convertLegacyPlugin', () => { + it('should convert a plain legacy plugin to a new plugin', () => { + expect( + convertLegacyPlugin(createLegacyPlugin({ id: 'test' }), { + extensions: [], + }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/BackstagePlugin", + "extensions": [], + "externalRoutes": {}, + "featureFlags": [], + "getExtension": [Function], + "id": "test", + "routes": {}, + "toString": [Function], + "version": "v1", + "withOverrides": [Function], + } + `); + }); + + it('should convert a legacy plugin with options to a new plugin', () => { + const apiRef = createApiRef({ id: 'plugin.test.client' }); + + const routeRef = createLegacyRouteRef({ id: 'test' }); + const extRouteRef = createLegacyExternalRouteRef({ id: 'testExt' }); + + const converted = convertLegacyPlugin( + createLegacyPlugin({ + id: 'test', + apis: [createApiFactory(apiRef, 'hello')], + routes: { test: routeRef }, + externalRoutes: { + testExt: extRouteRef, + }, + featureFlags: [{ name: 'test-flag' }], + }), + { + extensions: [ + PageBlueprint.make({ + params: { defaultPath: '/test', loader: async () => ({} as any) }, + }), + ], + }, + ); + + const internalConverted = toInternalBackstagePlugin(converted); + + expect(internalConverted.id).toBe('test'); + expect(internalConverted.routes).toEqual({ + test: routeRef, + }); + expect(internalConverted.externalRoutes).toEqual({ + testExt: extRouteRef, + }); + expect(internalConverted.featureFlags).toEqual([{ name: 'test-flag' }]); + expect(internalConverted.extensions.map(e => e.id)).toEqual([ + 'api:test/plugin.test.client', + 'page:test', + ]); + }); +}); diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts index 05beb4e469..db8daea9cd 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.ts +++ b/packages/core-compat-api/src/convertLegacyPlugin.ts @@ -16,6 +16,7 @@ import { BackstagePlugin as LegacyBackstagePlugin } from '@backstage/core-plugin-api'; import { + ApiBlueprint, ExtensionDefinition, BackstagePlugin as NewBackstagePlugin, createFrontendPlugin, @@ -27,11 +28,14 @@ export function convertLegacyPlugin( legacyPlugin: LegacyBackstagePlugin, options: { extensions: ExtensionDefinition[] }, ): NewBackstagePlugin { + const apiExtensions = Array.from(legacyPlugin.getApis()).map(factory => + ApiBlueprint.make({ name: factory.api.id, params: { factory } }), + ); return createFrontendPlugin({ id: legacyPlugin.getId(), featureFlags: [...legacyPlugin.getFeatureFlags()], routes: convertLegacyRouteRefs(legacyPlugin.routes ?? {}), externalRoutes: convertLegacyRouteRefs(legacyPlugin.externalRoutes ?? {}), - extensions: options.extensions, + extensions: [...apiExtensions, ...options.extensions], }); } From c6d86b7191495085ac16b4843471d2711e6a938a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 13:32:40 +0200 Subject: [PATCH 326/393] core-compat-api: test for convertLegacyPageExtension Signed-off-by: Patrik Oldsberg --- .../src/convertLegacyPageExtension.test.tsx | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 packages/core-compat-api/src/convertLegacyPageExtension.test.tsx diff --git a/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx b/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx new file mode 100644 index 0000000000..482474d580 --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { coreExtensionData } from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyPageExtension } from './convertLegacyPageExtension'; +import { convertLegacyRouteRef } from './convertLegacyRouteRef'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyPageExtension', () => { + it('should convert a page extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'ExamplePage', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyPageExtension(LegacyExtension); + expect(converted.kind).toBe('page'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/example'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + }); + + it('should convert a page extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'ExamplePage', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyPageExtension(LegacyExtension, { + name: 'other', + defaultPath: '/other', + }); + expect(converted.kind).toBe('page'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/other'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + }); +}); From 771bd7a5ffa7e245a64f8845e510184bbaeb8169 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 13:55:51 +0200 Subject: [PATCH 327/393] core-compat-api: async instead of Promise.resolve Signed-off-by: Patrik Oldsberg --- packages/core-compat-api/src/convertLegacyPageExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-compat-api/src/convertLegacyPageExtension.tsx b/packages/core-compat-api/src/convertLegacyPageExtension.tsx index 2ff5e0c574..7ef4e0faff 100644 --- a/packages/core-compat-api/src/convertLegacyPageExtension.tsx +++ b/packages/core-compat-api/src/convertLegacyPageExtension.tsx @@ -58,7 +58,7 @@ export function convertLegacyPageExtension( params: { defaultPath: overrides?.defaultPath ?? `/${kebabName}`, routeRef: mountPoint && convertLegacyRouteRef(mountPoint), - loader: () => Promise.resolve(compatWrapper(element)), + loader: async () => compatWrapper(element), }, }); } From e0e5cea175e924fd514086975ebd6652e9cfdc07 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:31:31 +0200 Subject: [PATCH 328/393] catalog-react: add initial legacy converters Signed-off-by: Patrik Oldsberg --- plugins/catalog-react/package.json | 1 + .../convertLegacyEntityCardExtension.tsx | 52 ++++++++++++++ .../convertLegacyEntityContentExtension.tsx | 69 +++++++++++++++++++ .../src/alpha/converters/index.ts | 18 +++++ plugins/catalog-react/src/alpha/index.ts | 2 + yarn.lock | 1 + 6 files changed, 143 insertions(+) create mode 100644 plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx create mode 100644 plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx create mode 100644 plugins/catalog-react/src/alpha/converters/index.ts diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index ef5d924200..f7b43a29b4 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -59,6 +59,7 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx new file mode 100644 index 0000000000..25ea778f0d --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { compatWrapper } from '@backstage/core-compat-api'; +import { getComponentData } from '@backstage/core-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import kebabCase from 'lodash/kebabCase'; +import React, { ComponentType } from 'react'; +import { EntityCardBlueprint } from '../blueprints'; + +/** @alpha */ +export function convertLegacyEntityCardExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityCardBlueprint.dataRefs.filterFunction.T + | typeof EntityCardBlueprint.dataRefs.filterExpression.T; + }, +): ExtensionDefinition { + const element = ; + + const extName = getComponentData(element, 'core.extensionName'); + if (!extName) { + throw new Error('Extension has no name'); + } + + const match = extName.match(/^Entity(.*)Card$/); + const name = match?.[1] ?? extName; + const kebabName = kebabCase(name); + + return EntityCardBlueprint.make({ + name: overrides?.name ?? kebabName, + params: { + filter: overrides?.filter, + loader: async () => compatWrapper(element), + }, + }); +} diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx new file mode 100644 index 0000000000..ea452a4d24 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { + getComponentData, + RouteRef as LegacyRouteRef, +} from '@backstage/core-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import kebabCase from 'lodash/kebabCase'; +import startCase from 'lodash/startCase'; +import React, { ComponentType } from 'react'; +import { EntityContentBlueprint } from '../blueprints'; + +/** @alpha */ +export function convertLegacyEntityContentExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityContentBlueprint.dataRefs.filterFunction.T + | typeof EntityContentBlueprint.dataRefs.filterExpression.T; + defaultPath?: string; + defaultTitle?: string; + }, +): ExtensionDefinition { + const element = ; + + const extName = getComponentData(element, 'core.extensionName'); + if (!extName) { + throw new Error('Extension has no name'); + } + + const mountPoint = getComponentData( + element, + 'core.mountPoint', + ); + + const match = extName.match(/^Entity(.*)Content$/); + const name = match?.[1] ?? extName; + const kebabName = kebabCase(name); + + return EntityContentBlueprint.make({ + name: overrides?.name ?? kebabName, + params: { + filter: overrides?.filter, + defaultPath: overrides?.defaultPath ?? `/${kebabName}`, + defaultTitle: overrides?.defaultTitle ?? startCase(name), + routeRef: mountPoint && convertLegacyRouteRef(mountPoint), + loader: async () => compatWrapper(element), + }, + }); +} diff --git a/plugins/catalog-react/src/alpha/converters/index.ts b/plugins/catalog-react/src/alpha/converters/index.ts new file mode 100644 index 0000000000..731ab846c0 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension'; +export { convertLegacyEntityContentExtension } from './convertLegacyEntityContentExtension'; diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts index 132b288008..3930fbde87 100644 --- a/plugins/catalog-react/src/alpha/index.ts +++ b/plugins/catalog-react/src/alpha/index.ts @@ -13,5 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './blueprints'; export * from './extensions'; +export * from './converters'; diff --git a/yarn.lock b/yarn.lock index 82b65687e6..f20870e0c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5918,6 +5918,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" + "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" From 769514e9922032f28a57c0cbe7e9bfe7062dc25d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:34:19 +0200 Subject: [PATCH 329/393] catalog-react: better name conversion for legacy converters Signed-off-by: Patrik Oldsberg --- .../convertLegacyEntityCardExtension.tsx | 26 ++++++++++++++----- .../convertLegacyEntityContentExtension.tsx | 26 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx index 25ea778f0d..afe5211d1b 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx @@ -15,9 +15,8 @@ */ import { compatWrapper } from '@backstage/core-compat-api'; -import { getComponentData } from '@backstage/core-plugin-api'; +import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; -import kebabCase from 'lodash/kebabCase'; import React, { ComponentType } from 'react'; import { EntityCardBlueprint } from '../blueprints'; @@ -38,12 +37,27 @@ export function convertLegacyEntityCardExtension( throw new Error('Extension has no name'); } - const match = extName.match(/^Entity(.*)Card$/); - const name = match?.[1] ?? extName; - const kebabName = kebabCase(name); + const plugin = getComponentData(element, 'core.plugin'); + const pluginId = plugin?.getId(); + + const match = extName.match(/^Entity(.*)Content$/); + const infix = match?.[1] ?? extName; + + let name: string | undefined = infix; + if ( + pluginId && + name + .toLocaleLowerCase('en-US') + .startsWith(pluginId.toLocaleLowerCase('en-US')) + ) { + name = name.slice(pluginId.length); + if (!name) { + name = undefined; + } + } return EntityCardBlueprint.make({ - name: overrides?.name ?? kebabName, + name: overrides?.name ?? name, params: { filter: overrides?.filter, loader: async () => compatWrapper(element), diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx index ea452a4d24..a44a6281da 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx @@ -19,6 +19,7 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { + BackstagePlugin, getComponentData, RouteRef as LegacyRouteRef, } from '@backstage/core-plugin-api'; @@ -52,16 +53,31 @@ export function convertLegacyEntityContentExtension( 'core.mountPoint', ); + const plugin = getComponentData(element, 'core.plugin'); + const pluginId = plugin?.getId(); + const match = extName.match(/^Entity(.*)Content$/); - const name = match?.[1] ?? extName; - const kebabName = kebabCase(name); + const infix = match?.[1] ?? extName; + + let name: string | undefined = infix; + if ( + pluginId && + name + .toLocaleLowerCase('en-US') + .startsWith(pluginId.toLocaleLowerCase('en-US')) + ) { + name = name.slice(pluginId.length); + if (!name) { + name = undefined; + } + } return EntityContentBlueprint.make({ - name: overrides?.name ?? kebabName, + name: overrides?.name ?? name, params: { filter: overrides?.filter, - defaultPath: overrides?.defaultPath ?? `/${kebabName}`, - defaultTitle: overrides?.defaultTitle ?? startCase(name), + defaultPath: overrides?.defaultPath ?? `/${kebabCase(infix)}`, + defaultTitle: overrides?.defaultTitle ?? startCase(infix), routeRef: mountPoint && convertLegacyRouteRef(mountPoint), loader: async () => compatWrapper(element), }, From 3f734557aceacc4e9870f879ca94fb01f9548d59 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:35:28 +0200 Subject: [PATCH 330/393] core-compat-api: use namespace pattern for APIs converted with convertLegacyPlugin Signed-off-by: Patrik Oldsberg --- packages/core-compat-api/src/convertLegacyPlugin.test.tsx | 2 +- packages/core-compat-api/src/convertLegacyPlugin.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 4e17c1d61b..657ee91725 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -84,7 +84,7 @@ describe('convertLegacyPlugin', () => { }); expect(internalConverted.featureFlags).toEqual([{ name: 'test-flag' }]); expect(internalConverted.extensions.map(e => e.id)).toEqual([ - 'api:test/plugin.test.client', + 'api:plugin.test.client', 'page:test', ]); }); diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts index db8daea9cd..5856108f29 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.ts +++ b/packages/core-compat-api/src/convertLegacyPlugin.ts @@ -29,7 +29,7 @@ export function convertLegacyPlugin( options: { extensions: ExtensionDefinition[] }, ): NewBackstagePlugin { const apiExtensions = Array.from(legacyPlugin.getApis()).map(factory => - ApiBlueprint.make({ name: factory.api.id, params: { factory } }), + ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), ); return createFrontendPlugin({ id: legacyPlugin.getId(), From 448d62ae8819a7e6dce5a79adb146fa258788194 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:36:57 +0200 Subject: [PATCH 331/393] core-compat-api: export new legacy converts + update API report Signed-off-by: Patrik Oldsberg --- packages/core-compat-api/api-report.md | 21 +++++++++++++++++++++ packages/core-compat-api/src/index.ts | 2 ++ 2 files changed, 23 insertions(+) diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 8d177b4dab..75b91cc055 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -8,6 +8,10 @@ import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/core-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; @@ -26,6 +30,23 @@ export function convertLegacyApp( rootElement: React_2.JSX.Element, ): FrontendFeature[]; +// @public (undocumented) +export function convertLegacyPageExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + defaultPath?: string; + }, +): ExtensionDefinition; + +// @public (undocumented) +export function convertLegacyPlugin( + legacyPlugin: BackstagePlugin, + options: { + extensions: ExtensionDefinition[]; + }, +): BackstagePlugin_2; + // @public export function convertLegacyRouteRef( ref: RouteRef, diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index 88e1892eac..7f99a7ab7a 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -18,6 +18,8 @@ export * from './compatWrapper'; export * from './apis'; export { convertLegacyApp } from './convertLegacyApp'; +export { convertLegacyPlugin } from './convertLegacyPlugin'; +export { convertLegacyPageExtension } from './convertLegacyPageExtension'; export { convertLegacyRouteRef, convertLegacyRouteRefs, From 3edf625c922ce9208810d0ebc24c5837a3c19dff Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:46:05 +0200 Subject: [PATCH 332/393] catalog-react: legacy converter tests + fixes Signed-off-by: Patrik Oldsberg --- .../convertLegacyEntityCardExtension.test.tsx | 129 +++++++++++++++++ .../convertLegacyEntityCardExtension.tsx | 4 +- ...nvertLegacyEntityContentExtension.test.tsx | 136 ++++++++++++++++++ .../convertLegacyEntityContentExtension.tsx | 1 + 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx create mode 100644 plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx new file mode 100644 index 0000000000..7dacd9c3e8 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx @@ -0,0 +1,129 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '../blueprints'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyEntityCardExtension', () => { + it('should convert an entity card extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension); + expect(converted.kind).toBe('entity-card'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + undefined, + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should convert an entity card extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension, { + name: 'other', + filter: 'my-filter', + }); + expect(converted.kind).toBe('entity-card'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + 'my-filter', + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should support various naming patterns for entity card extensions', async () => { + const withName = (name: string) => { + const converted = convertLegacyEntityCardExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ), + ); + return converted.name; + }; + + expect(withName('EntityTestCard')).toBe(undefined); + expect(withName('EntityTestTrimCard')).toBe('trim'); + expect(withName('EntityTeStTrimCard')).toBe('trim'); + expect(withName('EntityExampleCard')).toBe('example'); + expect(withName('EntityExAmpleCard')).toBe('ex-ample'); + expect(withName('ExampleCard')).toBe('example-card'); + }); +}); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx index afe5211d1b..76442f526d 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx @@ -19,6 +19,7 @@ import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import React, { ComponentType } from 'react'; import { EntityCardBlueprint } from '../blueprints'; +import kebabCase from 'lodash/kebabCase'; /** @alpha */ export function convertLegacyEntityCardExtension( @@ -40,7 +41,7 @@ export function convertLegacyEntityCardExtension( const plugin = getComponentData(element, 'core.plugin'); const pluginId = plugin?.getId(); - const match = extName.match(/^Entity(.*)Content$/); + const match = extName.match(/^Entity(.*)Card$/); const infix = match?.[1] ?? extName; let name: string | undefined = infix; @@ -55,6 +56,7 @@ export function convertLegacyEntityCardExtension( name = undefined; } } + name = name && kebabCase(name); return EntityCardBlueprint.make({ name: overrides?.name ?? name, diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx new file mode 100644 index 0000000000..6d7c8f0420 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { coreExtensionData } from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyEntityContentExtension } from './convertLegacyEntityContentExtension'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '../blueprints'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyEntityContentExtension', () => { + it('should convert an entity content extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleContent', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyEntityContentExtension(LegacyExtension); + expect(converted.kind).toBe('entity-content'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/example'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + undefined, + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should convert an entity content extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleContent', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyEntityContentExtension(LegacyExtension, { + name: 'other', + defaultPath: '/other', + defaultTitle: 'Other', + filter: 'my-filter', + }); + expect(converted.kind).toBe('entity-content'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/other'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + 'my-filter', + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should support various naming patterns for entity content extensions', async () => { + const withName = (name: string) => { + const converted = convertLegacyEntityContentExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ), + ); + return converted.name; + }; + + expect(withName('EntityTestContent')).toBe(undefined); + expect(withName('EntityTestTrimContent')).toBe('trim'); + expect(withName('EntityTeStTrimContent')).toBe('trim'); + expect(withName('EntityExampleContent')).toBe('example'); + expect(withName('EntityExAmpleContent')).toBe('ex-ample'); + expect(withName('ExampleContent')).toBe('example-content'); + }); +}); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx index a44a6281da..e44b5671fc 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx @@ -71,6 +71,7 @@ export function convertLegacyEntityContentExtension( name = undefined; } } + name = name && kebabCase(name); return EntityContentBlueprint.make({ name: overrides?.name ?? name, From c5cc0562e161dd6f56556675fd2b6955747b9566 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:46:23 +0200 Subject: [PATCH 333/393] app-next: add legacy conversion of TechDocs Signed-off-by: Patrik Oldsberg --- packages/app-next/src/App.tsx | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index c41f33e01b..170decee42 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -29,7 +29,12 @@ import { createExtensionOverrides, ApiBlueprint, } from '@backstage/frontend-plugin-api'; -import techdocsPlugin from '@backstage/plugin-techdocs/alpha'; +import { + techdocsPlugin, + TechDocsIndexPage, + TechDocsReaderPage, + EntityTechdocsContent, +} from '@backstage/plugin-techdocs'; import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; import { homePage } from './HomePage'; import { convertLegacyApp } from '@backstage/core-compat-api'; @@ -45,6 +50,9 @@ import { } from '@backstage/integration-react'; import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; import { signInPageOverrides } from './overrides/SignInPage'; +import { convertLegacyPlugin } from '@backstage/core-compat-api'; +import { convertLegacyPageExtension } from '@backstage/core-compat-api'; +import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; /* @@ -75,6 +83,17 @@ TODO: /* app.tsx */ +const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, { + extensions: [ + // TODO: We likely also need a way to convert an entire tree similar to collectLegacyRoutes + convertLegacyPageExtension(TechDocsIndexPage), + convertLegacyPageExtension(TechDocsReaderPage, { + defaultPath: '/docs/:namespace/:kind/:name/*', + }), + convertLegacyEntityContentExtension(EntityTechdocsContent), + ], +}); + const homePageExtension = createExtension({ name: 'myhomepage', attachTo: { id: 'page:home', input: 'props' }, @@ -114,7 +133,7 @@ const collectedLegacyPlugins = convertLegacyApp( const app = createApp({ features: [ pagesPlugin, - techdocsPlugin, + convertedTechdocsPlugin, userSettingsPlugin, homePlugin, appVisualizerPlugin, From eef894019440cda052f2a4853acc00e63e3b57e5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:48:06 +0200 Subject: [PATCH 334/393] catalog-react: update API report Signed-off-by: Patrik Oldsberg --- plugins/catalog-react/api-report-alpha.md | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index 6f801ed45f..ffd33aa348 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -7,6 +7,7 @@ import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; @@ -95,6 +96,30 @@ export const catalogReactTranslationRef: TranslationRef< } >; +// @alpha (undocumented) +export function convertLegacyEntityCardExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityCardBlueprint.dataRefs.filterFunction.T + | typeof EntityCardBlueprint.dataRefs.filterExpression.T; + }, +): ExtensionDefinition; + +// @alpha (undocumented) +export function convertLegacyEntityContentExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityContentBlueprint.dataRefs.filterFunction.T + | typeof EntityContentBlueprint.dataRefs.filterExpression.T; + defaultPath?: string; + defaultTitle?: string; + }, +): ExtensionDefinition; + // @alpha @deprecated (undocumented) export function createEntityCardExtension< TConfig extends { From 519b8e059b39bfaf7024f35a9a24a8a0e16215d3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 14:51:40 +0200 Subject: [PATCH 335/393] changesets: add changesets for new legacy converters Signed-off-by: Patrik Oldsberg --- .changeset/silent-camels-walk.md | 5 +++++ .changeset/thirty-balloons-end.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/silent-camels-walk.md create mode 100644 .changeset/thirty-balloons-end.md diff --git a/.changeset/silent-camels-walk.md b/.changeset/silent-camels-walk.md new file mode 100644 index 0000000000..244a615335 --- /dev/null +++ b/.changeset/silent-camels-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Added utilities for converting existing entity card and content extensions to the new frontend system. This is in particular useful when used in combination with the new `convertLegacyPlugin` utility from `@backstage/core-compat-api`. diff --git a/.changeset/thirty-balloons-end.md b/.changeset/thirty-balloons-end.md new file mode 100644 index 0000000000..f6e252988e --- /dev/null +++ b/.changeset/thirty-balloons-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Added new utilities for converting legacy plugins and extensions to the new system. The `convertLegacyPlugin` option will convert an existing plugin to the new system, although you need to supply extensions for the plugin yourself. To help out with this, there is also a new `convertLegacyPageExtension` which converts an existing page extension to the new system. From 6bd6fdad12aafd94a005528ea78a6864dda07f8f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Aug 2024 15:29:45 +0200 Subject: [PATCH 336/393] app-backend: Deprecate createRouter Signed-off-by: Johan Haals --- .changeset/spicy-jars-hear.md | 5 +++++ plugins/app-backend/api-report.md | 8 ++++---- plugins/app-backend/src/service/router.ts | 20 +++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 .changeset/spicy-jars-hear.md diff --git a/.changeset/spicy-jars-hear.md b/.changeset/spicy-jars-hear.md new file mode 100644 index 0000000000..9006501ee1 --- /dev/null +++ b/.changeset/spicy-jars-hear.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Deprecate `createRouter` and its options in favour of the new backend system. diff --git a/plugins/app-backend/api-report.md b/plugins/app-backend/api-report.md index 9ef0f97dab..690ce1c285 100644 --- a/plugins/app-backend/api-report.md +++ b/plugins/app-backend/api-report.md @@ -6,22 +6,22 @@ import { AuthService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import express from 'express'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginDatabaseManager } from '@backstage/backend-common'; -// @public (undocumented) +// @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { appPackageName: string; // (undocumented) auth?: AuthService; // (undocumented) config: Config; - database?: PluginDatabaseManager; + database?: DatabaseService; disableConfigInjection?: boolean; // (undocumented) httpAuth?: HttpAuthService; diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index 8ec871df81..7633968f06 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -14,11 +14,11 @@ * limitations under the License. */ +import { notFoundHandler } from '@backstage/backend-common'; import { - notFoundHandler, - PluginDatabaseManager, -} from '@backstage/backend-common'; -import { resolvePackagePath } from '@backstage/backend-plugin-api'; + DatabaseService, + resolvePackagePath, +} from '@backstage/backend-plugin-api'; import { AppConfig, Config } from '@backstage/config'; import helmet from 'helmet'; import express from 'express'; @@ -47,7 +47,10 @@ import { AuthenticationError } from '@backstage/errors'; // express uses mime v1 while we only have types for mime v2 type Mime = { lookup(arg0: string): string }; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { config: Config; logger: LoggerService; @@ -59,7 +62,7 @@ export interface RouterOptions { * * This is a built-in alternative to using a `staticFallbackHandler`. */ - database?: PluginDatabaseManager; + database?: DatabaseService; /** * The name of the app package that content should be served from. The same app package should be @@ -102,7 +105,10 @@ export interface RouterOptions { schema?: ConfigSchema; } -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export async function createRouter( options: RouterOptions, ): Promise { From cc9a7a558b68498737438a07ce31b2982bcd59b2 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Aug 2024 15:41:34 +0200 Subject: [PATCH 337/393] auth-backend: deprecate createRouter Signed-off-by: Johan Haals --- .changeset/thirty-dogs-wave.md | 5 +++++ plugins/auth-backend/api-report.md | 14 ++++++------ plugins/auth-backend/src/service/router.ts | 26 +++++++++++++--------- 3 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 .changeset/thirty-dogs-wave.md diff --git a/.changeset/thirty-dogs-wave.md b/.changeset/thirty-dogs-wave.md new file mode 100644 index 0000000000..b8c4728a32 --- /dev/null +++ b/.changeset/thirty-dogs-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Deprecated `createRouter` and its router options in favour of the new backend system. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 56c2da3a08..29086cf86b 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -20,6 +20,7 @@ import { ClientAuthResponse } from '@backstage/plugin-auth-node'; import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; import { Config } from '@backstage/config'; import { CookieConfigurer as CookieConfigurer_2 } from '@backstage/plugin-auth-node'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import { decodeOAuthState } from '@backstage/plugin-auth-node'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { encodeOAuthState } from '@backstage/plugin-auth-node'; @@ -33,14 +34,13 @@ import { OAuth2ProxyResult as OAuth2ProxyResult_2 } from '@backstage/plugin-auth import { OAuthEnvironmentHandler as OAuthEnvironmentHandler_2 } from '@backstage/plugin-auth-node'; import { OAuthState as OAuthState_2 } from '@backstage/plugin-auth-node'; import { OidcAuthResult as OidcAuthResult_2 } from '@backstage/plugin-auth-backend-module-oidc-provider'; -import { PluginDatabaseManager } from '@backstage/backend-common'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { prepareBackstageIdentityResponse as prepareBackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; import { Profile } from 'passport'; import { ProfileInfo as ProfileInfo_2 } from '@backstage/plugin-auth-node'; import { SignInInfo as SignInInfo_2 } from '@backstage/plugin-auth-node'; import { SignInResolver as SignInResolver_2 } from '@backstage/plugin-auth-node'; import { TokenManager } from '@backstage/backend-common'; +import { TokenManagerService } from '@backstage/backend-plugin-api'; import { TokenParams as TokenParams_2 } from '@backstage/plugin-auth-node'; import { UserEntity } from '@backstage/catalog-model'; import { WebMessageResponse as WebMessageResponse_2 } from '@backstage/plugin-auth-node'; @@ -193,7 +193,7 @@ export function createAuthProviderIntegration< // @public (undocumented) export function createOriginFilter(config: Config): (origin: string) => boolean; -// @public (undocumented) +// @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; // @public @@ -647,7 +647,7 @@ export const providers: Readonly<{ // @public @deprecated (undocumented) export const readState: typeof decodeOAuthState; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { // (undocumented) auth?: AuthService; @@ -656,11 +656,11 @@ export interface RouterOptions { // (undocumented) config: Config; // (undocumented) - database: PluginDatabaseManager; + database: DatabaseService; // (undocumented) disableDefaultProviderFactories?: boolean; // (undocumented) - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; // (undocumented) httpAuth?: HttpAuthService; // (undocumented) @@ -672,7 +672,7 @@ export interface RouterOptions { // (undocumented) tokenFactoryAlgorithm?: string; // (undocumented) - tokenManager: TokenManager; + tokenManager: TokenManagerService; } // @public (undocumented) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 4bb42c3abd..033f9f8d21 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -19,17 +19,15 @@ import Router from 'express-promise-router'; import cookieParser from 'cookie-parser'; import { AuthService, + DatabaseService, + DiscoveryService, HttpAuthService, LoggerService, + TokenManagerService, } from '@backstage/backend-plugin-api'; import { defaultAuthProviderFactories } from '../providers'; import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; -import { - createLegacyAuthAdapters, - PluginDatabaseManager, - PluginEndpointDiscovery, - TokenManager, -} from '@backstage/backend-common'; +import { createLegacyAuthAdapters } from '@backstage/backend-common'; import { NotFoundError } from '@backstage/errors'; import { CatalogApi } from '@backstage/catalog-client'; import { @@ -49,13 +47,16 @@ import { StaticKeyStore } from '../identity/StaticKeyStore'; import { Config } from '@backstage/config'; import { bindProviderRouters, ProviderFactories } from '../providers/router'; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { logger: LoggerService; - database: PluginDatabaseManager; + database: DatabaseService; config: Config; - discovery: PluginEndpointDiscovery; - tokenManager: TokenManager; + discovery: DiscoveryService; + tokenManager: TokenManagerService; auth?: AuthService; httpAuth?: HttpAuthService; tokenFactoryAlgorithm?: string; @@ -65,7 +66,10 @@ export interface RouterOptions { ownershipResolver?: AuthOwnershipResolver; } -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export async function createRouter( options: RouterOptions, ): Promise { From f15d1fb1f12d1fa6e3165f33fc362ca0229a98b1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Aug 2024 15:44:51 +0200 Subject: [PATCH 338/393] chore: switch from config to RootConfigService Signed-off-by: Johan Haals --- plugins/app-backend/api-report.md | 4 ++-- plugins/app-backend/src/service/router.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/app-backend/api-report.md b/plugins/app-backend/api-report.md index 690ce1c285..bb0ebb67db 100644 --- a/plugins/app-backend/api-report.md +++ b/plugins/app-backend/api-report.md @@ -4,12 +4,12 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; import { DatabaseService } from '@backstage/backend-plugin-api'; import express from 'express'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; // @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; @@ -20,7 +20,7 @@ export interface RouterOptions { // (undocumented) auth?: AuthService; // (undocumented) - config: Config; + config: RootConfigService; database?: DatabaseService; disableConfigInjection?: boolean; // (undocumented) diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index 7633968f06..b205f16203 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -18,8 +18,9 @@ import { notFoundHandler } from '@backstage/backend-common'; import { DatabaseService, resolvePackagePath, + RootConfigService, } from '@backstage/backend-plugin-api'; -import { AppConfig, Config } from '@backstage/config'; +import { AppConfig } from '@backstage/config'; import helmet from 'helmet'; import express from 'express'; import Router from 'express-promise-router'; @@ -52,7 +53,7 @@ type Mime = { lookup(arg0: string): string }; * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export interface RouterOptions { - config: Config; + config: RootConfigService; logger: LoggerService; auth?: AuthService; httpAuth?: HttpAuthService; From 80a3df73fcced39c7625d8b0ddb003b322028022 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Aug 2024 15:48:59 +0200 Subject: [PATCH 339/393] Remove config Dependency Signed-off-by: Johan Haals --- plugins/auth-backend/api-report.md | 3 ++- plugins/auth-backend/package.json | 1 - plugins/auth-backend/src/service/router.ts | 4 ++-- yarn.lock | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 29086cf86b..f801c92597 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -37,6 +37,7 @@ import { OidcAuthResult as OidcAuthResult_2 } from '@backstage/plugin-auth-backe import { prepareBackstageIdentityResponse as prepareBackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; import { Profile } from 'passport'; import { ProfileInfo as ProfileInfo_2 } from '@backstage/plugin-auth-node'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { SignInInfo as SignInInfo_2 } from '@backstage/plugin-auth-node'; import { SignInResolver as SignInResolver_2 } from '@backstage/plugin-auth-node'; import { TokenManager } from '@backstage/backend-common'; @@ -654,7 +655,7 @@ export interface RouterOptions { // (undocumented) catalogApi?: CatalogApi; // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) database: DatabaseService; // (undocumented) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 9c7b205352..7402e0d242 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -47,7 +47,6 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^", "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^", diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 033f9f8d21..b954eb4dbb 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -23,6 +23,7 @@ import { DiscoveryService, HttpAuthService, LoggerService, + RootConfigService, TokenManagerService, } from '@backstage/backend-plugin-api'; import { defaultAuthProviderFactories } from '../providers'; @@ -44,7 +45,6 @@ import { readBackstageTokenExpiration } from './readBackstageTokenExpiration'; import { TokenIssuer } from '../identity/types'; import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; -import { Config } from '@backstage/config'; import { bindProviderRouters, ProviderFactories } from '../providers/router'; /** @@ -54,7 +54,7 @@ import { bindProviderRouters, ProviderFactories } from '../providers/router'; export interface RouterOptions { logger: LoggerService; database: DatabaseService; - config: Config; + config: RootConfigService; discovery: DiscoveryService; tokenManager: TokenManagerService; auth?: AuthService; diff --git a/yarn.lock b/yarn.lock index 301d7de22f..8956a0159e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5146,7 +5146,6 @@ __metadata: "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^" "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^" From 4a8c91ec36799343a1204a71ec772764dfb11f1b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Aug 2024 16:54:36 +0200 Subject: [PATCH 340/393] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- .../architecture/25-extension-overrides.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 4bbb90edfb..d47e2697a9 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -16,7 +16,7 @@ In general, most features should have a good level of customization built into ## Overriding an extension -Every extension created with `createExtension` comes with an `override` method, including those created from an [extension blueprint](./23-extension-blueprints.md). The `override` method **creates a new extension**, it does not mutate the existing extensions. This new extension in created in such a way that if it is installed adjacent to the existing extension, it will take precedence and override the existing extension. While the `override` method does create new extension instances, it is not intended to be used as a way to create multiple new extensions from a base template, for that use-case you will want to use an [extension blueprint](./23-extension-blueprints.md) instead. +Every extension created with `createExtension` comes with an `override` method, including those created from an [extension blueprint](./23-extension-blueprints.md). The `override` method **creates a new extension**, it does not mutate the existing extension. This new extension in created in such a way that if it is installed adjacent to the existing extension, it will take precedence and override the existing extension. While the `override` method does create new extension instances, it is not intended to be used as a way to create multiple new extensions from a base template, for that use-case you will want to use an [extension blueprint](./23-extension-blueprints.md) instead. The following is an example of calling the `.override(...)` method on an extension: @@ -81,7 +81,7 @@ Note the `yield*` expression, which forwards all values from the provided iterab ## Overriding declared outputs -When overriding an extension you can provide a new output declaration. This **replaces** any existing output declaration, which means that you want to forward any of the original output you will need to declare it again. The following example shows how to override an extension and replace the output declaration: +When overriding an extension you can provide a new output declaration. This **replaces** any existing output declaration, which means that if you want to forward any of the original output you will need to declare it again. The following example shows how to override an extension and replace the output declaration: ```tsx // Original extension @@ -138,7 +138,7 @@ const myOverrideExtension = myExtension.override({ ## Overriding configuration schema -Overriding the configuration schema works very similar to overriding the declared inputs. You can define new configuration fields that will be merged with the existing ones, but you can not re-declare existing fields. The following example shows how to override an extension and add a new configuration field: +Overriding the configuration schema works very similarly to overriding the declared inputs. You can define new configuration fields that will be merged with the existing ones, but you can not re-declare existing fields. The following example shows how to override an extension and add a new configuration field: ```tsx const exampleExtension = createExtension({ @@ -251,7 +251,7 @@ const overrideExtension = exampleExtension.override({ content: inputs.content, // Sort items input by their extension ID items: inputs.items.toSorted((a, b) => - a.node.spec.id.localCompare(b.node.spec.id), + a.node.spec.id.localeCompare(b.node.spec.id), ), }, }); @@ -261,7 +261,7 @@ const overrideExtension = exampleExtension.override({ ## Installing override extension in an app -To install extension overrides in a Backstage app you should use `plugin.withOverrides` whenever you are overriding or adding extensions to for a plugin. See the section on [overriding a plugin](./15-plugins.md#overriding-a-plugin) for more information. +To install extension overrides in a Backstage app you should use `plugin.withOverrides` whenever you are overriding or adding extensions for a plugin. See the section on [overriding a plugin](./15-plugins.md#overriding-a-plugin) for more information. There is also a `createExtensionOverrides` function that can be used to install a collection of standalone extensions in an app. This method will be replaced with a different mechanism in the future, but for now remains the only way to override the built-in extensions in the app or to package extensions for a plugin package separate from the plugin itself. From 46e5e554f38a0b8b5e49094d0c5bcada58b05d2e Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Fri, 16 Aug 2024 19:13:32 -0400 Subject: [PATCH 341/393] change scaffolder secret widget to use textfield Signed-off-by: Stephen Glass --- .changeset/clever-laws-raise.md | 5 ++++ .../components/SecretWidget/SecretWidget.tsx | 29 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 .changeset/clever-laws-raise.md diff --git a/.changeset/clever-laws-raise.md b/.changeset/clever-laws-raise.md new file mode 100644 index 0000000000..12879621a4 --- /dev/null +++ b/.changeset/clever-laws-raise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Change scaffolder secret widget to use `TextField` component for more flexibility in theme overrides. diff --git a/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx b/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx index e2d139f98b..0b8ca8da3b 100644 --- a/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx +++ b/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx @@ -16,8 +16,7 @@ import { WidgetProps } from '@rjsf/utils'; import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react'; -import InputLabel from '@material-ui/core/InputLabel'; -import Input from '@material-ui/core/Input'; +import TextField from '@material-ui/core/TextField'; import React from 'react'; /** @@ -35,19 +34,17 @@ export const SecretWidget = ( } = props; return ( - <> - {title} - { - onChange(Array(e.target?.value.length).fill('*').join('')); - setSecrets({ [name]: e.target?.value }); - }} - value={secrets[name] ?? ''} - type="password" - autoComplete="off" - /> - + { + onChange(Array(e.target.value.length).fill('*').join('')); + setSecrets({ [name]: e.target.value }); + }} + value={secrets[name] ?? ''} + type="password" + autoComplete="off" + /> ); }; From 98ed387a240ee0767c63027f8caa20ee2e9ba76c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 17 Aug 2024 14:01:52 +0200 Subject: [PATCH 342/393] frontend-test-utils: remove dead code Signed-off-by: Patrik Oldsberg --- .../src/app/createExtensionTester.test.tsx | 58 ------------------- 1 file changed, 58 deletions(-) diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 14ffd05ada..7467f61c4e 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -368,62 +368,4 @@ describe('createExtensionTester', () => { expect([test, test2, test3]).toBeDefined(); }); - - // TODO: this should be implemented - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should allow querying an extension and getting outputs', () => { - const extension = createExtension({ - namespace: 'test', - name: 'e1', - attachTo: { id: 'ignored', input: 'ignored' }, - output: [stringDataRef], - inputs: { - input: createExtensionInput([stringDataRef], { singleton: true }), - }, - factory: ({ inputs }) => [ - stringDataRef(`nest-${inputs.input.get(stringDataRef)}`), - ], - }); - - const extension2 = createExtension({ - namespace: 'test', - name: 'e2', - attachTo: { id: 'test/e1', input: 'blob' }, - output: [stringDataRef], - factory: () => [stringDataRef('test-text')], - }); - - const tester = createExtensionTester(extension).add(extension2); - - expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); - expect(tester.query(extension2).get(stringDataRef)).toBe('test-text'); - // @ts-expect-error - expect(tester.query(extension).input('input').data(stringDataRef)).toBe( - 'nest-test-text', - ); - }); - - // TODO: this should be implemented - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should allow defining inputs to extensions without a corresponding extension definition', () => { - const extension = createExtension({ - namespace: 'test', - name: 'e1', - attachTo: { id: 'ignored', input: 'ignored' }, - output: [stringDataRef], - inputs: { - input: createExtensionInput([stringDataRef], { singleton: true }), - }, - factory: ({ inputs }) => [ - stringDataRef(`nest-${inputs.input.get(stringDataRef)}`), - ], - }); - - const tester = createExtensionTester(extension, { - // @ts-expect-error - inputs: { input: 'test-text' }, - }); - - expect(tester.query(extension).get(stringDataRef)).toBe('nest-test-text'); - }); }); From 37f12918a6cdf98a5d5e1f24e2758af597e5f56c Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 08:12:43 +0200 Subject: [PATCH 343/393] chore: re-add config dependency Signed-off-by: Johan Haals --- plugins/auth-backend/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 7402e0d242..9c7b205352 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -47,6 +47,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^", "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 8956a0159e..301d7de22f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5146,6 +5146,7 @@ __metadata: "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^" "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^" From 4ea354f48099709628dde91e486265d83a635105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 19 Aug 2024 09:29:38 +0200 Subject: [PATCH 344/393] add a signer claim to the aws alb auth provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/eleven-dolphins-divide.md | 5 ++ docs/auth/aws-alb/provider.md | 12 +++-- .../config.d.ts | 20 ++++++- .../src/authenticator.test.ts | 53 +++++++++++++++---- .../src/authenticator.ts | 9 ++-- .../src/types.ts | 1 + 6 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 .changeset/eleven-dolphins-divide.md diff --git a/.changeset/eleven-dolphins-divide.md b/.changeset/eleven-dolphins-divide.md new file mode 100644 index 0000000000..b3f815dba6 --- /dev/null +++ b/.changeset/eleven-dolphins-divide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +--- + +Added a `signer` configuration option to validate against the token claims diff --git a/docs/auth/aws-alb/provider.md b/docs/auth/aws-alb/provider.md index 8f21b7dbf0..a3974c895e 100644 --- a/docs/auth/aws-alb/provider.md +++ b/docs/auth/aws-alb/provider.md @@ -11,14 +11,18 @@ and get the user seamlessly authenticated. ## Configuration The provider configuration can be added to your `app-config.yaml` under the root -`auth` configuration: +`auth` configuration, similar to the following example: ```yaml title="app-config.yaml" auth: providers: awsalb: - issuer: 'https://example.okta.com/oauth2/default' # optional - region: 'us-west-2' # required, use your actual region here + # this is the URL of the IdP you configured + issuer: 'https://example.okta.com/oauth2/default' + # this is the ARN of your ALB instance + signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + # this is the region where your ALB instance resides + region: 'us-west-2' signIn: resolvers: # typically you would pick one of these @@ -26,6 +30,8 @@ auth: - resolver: emailLocalPartMatchingUserEntityName ``` +Ensure that you have set the signer correctly. It is also recommended that you restrict your target groups' security policy to only accept connections from that ALB. + ### Resolvers This provider includes several resolvers out of the box that you can use: diff --git a/plugins/auth-backend-module-aws-alb-provider/config.d.ts b/plugins/auth-backend-module-aws-alb-provider/config.d.ts index 5a3e90cf6b..d76ae5bada 100644 --- a/plugins/auth-backend-module-aws-alb-provider/config.d.ts +++ b/plugins/auth-backend-module-aws-alb-provider/config.d.ts @@ -19,7 +19,25 @@ export interface Config { providers?: { /** @visibility frontend */ awsalb?: { - issuer?: string; + /** + * The issuer IdP URL that was configured in your ALB; this corresponds + * to the `iss` claim in your tokens. + * + * @example https://example.okta.com/oauth2/default + */ + issuer: string; + /** + * The ARN of the ALB that signs the tokens; this corresponds to the + * `signer` claim in your tokens. + * + * @example arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456 + */ + signer?: string; + /** + * The AWS region where the ALB is located. + * + * @example us-east-2 + */ region: string; signIn?: { resolvers: Array< diff --git a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts index e4700d10dd..c0e32d37a3 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts @@ -21,7 +21,7 @@ import { ALB_JWT_HEADER, awsAlbAuthenticator, } from './authenticator'; -import { Config } from '@backstage/config'; +import { ConfigReader } from '@backstage/config'; import { AuthenticationError } from '@backstage/errors'; describe('AwsAlbProvider', () => { @@ -35,6 +35,7 @@ describe('AwsAlbProvider', () => { email: 'user.name@email.test', exp: Date.now() + 10000, iss: 'ISSUER_URL', + signer: 'SIGNER_ARN', }; const signingKey = new TextEncoder().encode('signingKey'); let mockJwt: string; @@ -87,6 +88,7 @@ describe('AwsAlbProvider', () => { { req: mockRequest }, { issuer: 'ISSUER_URL', + signer: 'SIGNER_ARN', getKey: jest.fn().mockResolvedValue(signingKey), }, ); @@ -114,12 +116,13 @@ describe('AwsAlbProvider', () => { }); }); }); + describe('should fail when', () => { it('Access token is missing', async () => { await expect( awsAlbAuthenticator.authenticate( { req: mockRequestWithoutAccessToken }, - { issuer: 'ISSUER_URL', getKey: jest.fn() }, + { issuer: 'ISSUER_URL', signer: 'SIGNER_ARN', getKey: jest.fn() }, ), ).rejects.toThrow(AuthenticationError); }); @@ -128,7 +131,7 @@ describe('AwsAlbProvider', () => { await expect( awsAlbAuthenticator.authenticate( { req: mockRequestWithoutJwt }, - { issuer: 'ISSUER_URL', getKey: jest.fn() }, + { issuer: 'ISSUER_URL', signer: 'SIGNER_ARN', getKey: jest.fn() }, ), ).rejects.toThrow(AuthenticationError); }); @@ -137,7 +140,7 @@ describe('AwsAlbProvider', () => { await expect( awsAlbAuthenticator.authenticate( { req: mockRequestWithInvalidJwt }, - { issuer: 'ISSUER_URL', getKey: jest.fn() }, + { issuer: 'ISSUER_URL', signer: 'SIGNER_ARN', getKey: jest.fn() }, ), ).rejects.toThrow( 'Exception occurred during JWT processing: JWSInvalid: Invalid Compact JWS', @@ -164,6 +167,7 @@ describe('AwsAlbProvider', () => { { req }, { issuer: 'ISSUER_URL', + signer: undefined, getKey: jest.fn().mockResolvedValue(signingKey), }, ), @@ -192,6 +196,36 @@ describe('AwsAlbProvider', () => { { req }, { issuer: 'ISSUER_URL', + signer: 'SIGNER_ARN', + getKey: jest.fn().mockResolvedValue(signingKey), + }, + ), + ).rejects.toThrow( + 'Exception occurred during JWT processing: AuthenticationError: Issuer mismatch on JWT token', + ); + }); + + it('signer is invalid', async () => { + const jwt = await new SignJWT({ signer: 'INVALID_SIGNER_ARN' }) + .setProtectedHeader({ alg: 'HS256' }) + .sign(signingKey); + const req = { + header: jest.fn(name => { + if (name === ALB_JWT_HEADER) { + return jwt; + } else if (name === ALB_ACCESS_TOKEN_HEADER) { + return mockAccessToken; + } + return undefined; + }), + } as unknown as express.Request; + + await expect( + awsAlbAuthenticator.authenticate( + { req }, + { + issuer: 'ISSUER_URL', + signer: 'SIGNER_ARN', getKey: jest.fn().mockResolvedValue(signingKey), }, ), @@ -200,15 +234,14 @@ describe('AwsAlbProvider', () => { ); }); }); + describe('should initialize', () => { it('with default options', async () => { const config = { - config: { - getString: jest - .fn() - .mockReturnValueOnce('ISSUER_URL') - .mockReturnValueOnce('TEST_REGION'), - } as unknown as Config, + config: new ConfigReader({ + issuer: 'ISSUER_URL', + region: 'TEST_REGION', + }), }; expect(awsAlbAuthenticator.initialize(config)).toEqual({ diff --git a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts index e7e0a7d76d..f62fe4175b 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts @@ -36,12 +36,13 @@ export const awsAlbAuthenticator = createProxyAuthenticator({ }, initialize({ config }) { const issuer = config.getString('issuer'); + const signer = config.getOptionalString('signer'); const region = config.getString('region'); const keyCache = new NodeCache({ stdTTL: 3600 }); const getKey = provisionKeyCache(region, keyCache); - return { issuer, getKey }; + return { issuer, signer, getKey }; }, - async authenticate({ req }, { issuer, getKey }) { + async authenticate({ req }, { issuer, signer, getKey }) { const jwt = req.header(ALB_JWT_HEADER); const accessToken = req.header(ALB_ACCESS_TOKEN_HEADER); @@ -61,8 +62,10 @@ export const awsAlbAuthenticator = createProxyAuthenticator({ const verifyResult = await jwtVerify(jwt, getKey); const claims = verifyResult.payload as AwsAlbClaims; - if (issuer && claims?.iss !== issuer) { + if (claims?.iss !== issuer) { throw new AuthenticationError('Issuer mismatch on JWT token'); + } else if (signer && claims?.signer !== signer) { + throw new AuthenticationError('Signer mismatch on JWT token'); } const fullProfile: PassportProfile = { diff --git a/plugins/auth-backend-module-aws-alb-provider/src/types.ts b/plugins/auth-backend-module-aws-alb-provider/src/types.ts index c45640851c..679535804e 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/types.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/types.ts @@ -38,4 +38,5 @@ export type AwsAlbClaims = { email: string; exp: number; iss: string; + signer: string; }; From bae0120960ac5c0c7cb1dc3dc4d02b58731d456c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 19 Aug 2024 09:42:47 +0200 Subject: [PATCH 345/393] reveiw comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/eleven-dolphins-divide.md | 21 ++++++++++++++++++- .../api-report.md | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.changeset/eleven-dolphins-divide.md b/.changeset/eleven-dolphins-divide.md index b3f815dba6..a35702323c 100644 --- a/.changeset/eleven-dolphins-divide.md +++ b/.changeset/eleven-dolphins-divide.md @@ -2,4 +2,23 @@ '@backstage/plugin-auth-backend-module-aws-alb-provider': patch --- -Added a `signer` configuration option to validate against the token claims +Added a `signer` configuration option to validate against the token claims. We strongly recommend that you set this value (typically on the format `arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456`) to ensure that the auth provider can safely check the authenticity of any incoming tokens. + +Example: + +```diff + auth: + providers: + awsalb: + # this is the URL of the IdP you configured + issuer: 'https://example.okta.com/oauth2/default' + # this is the ARN of your ALB instance ++ signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + # this is the region where your ALB instance resides + region: 'us-west-2' + signIn: + resolvers: + # typically you would pick one of these + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName +``` diff --git a/plugins/auth-backend-module-aws-alb-provider/api-report.md b/plugins/auth-backend-module-aws-alb-provider/api-report.md index 7f4531419a..78e7522069 100644 --- a/plugins/auth-backend-module-aws-alb-provider/api-report.md +++ b/plugins/auth-backend-module-aws-alb-provider/api-report.md @@ -21,6 +21,7 @@ export default authModuleAwsAlbProvider; export const awsAlbAuthenticator: ProxyAuthenticator< { issuer: string; + signer: string | undefined; getKey: (header: JWTHeaderParameters) => Promise; }, AwsAlbResult, From 6795e33e7838a2ab342876b713a4f0021b54b341 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 19 Aug 2024 09:43:37 +0200 Subject: [PATCH 346/393] docs: mark backend-common as deprecated Signed-off-by: Camila Belo --- .changeset/famous-cougars-walk.md | 5 +++++ packages/backend-common/README.md | 3 +++ packages/backend-common/src/index.ts | 3 +++ 3 files changed, 11 insertions(+) create mode 100644 .changeset/famous-cougars-walk.md diff --git a/.changeset/famous-cougars-walk.md b/.changeset/famous-cougars-walk.md new file mode 100644 index 0000000000..465b03f86d --- /dev/null +++ b/.changeset/famous-cougars-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +This package is marked as `deprecated` and will be removed in a near future, please follow the deprecated instructions for the exports you still use. diff --git a/packages/backend-common/README.md b/packages/backend-common/README.md index 71a15bfb5a..e9f2f43e83 100644 --- a/packages/backend-common/README.md +++ b/packages/backend-common/README.md @@ -1,5 +1,8 @@ # @backstage/backend-common +> [!CAUTION] +> This package is deprecated and will be removed in a near future, so please follow the deprecated instructions for the exports you still use. + Common functionality library for Backstage backends, implementing logging, error handling and similar. diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index ad5621b5a9..e08bef3048 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -17,6 +17,9 @@ /** * Common functionality library for Backstage backends * + * @remarks + * This package is deprecated and will be removed in a near future, so please follow the deprecated instructions for the exports you still use. + * * @packageDocumentation */ From f58c71da9d81e769cbea110111c468a4200e0b31 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 19 Aug 2024 12:04:33 +0200 Subject: [PATCH 347/393] chore: fixing issue with the vizualizer Signed-off-by: blam --- .../AppVisualizerPage/DetailedVisualizer.tsx | 27 +++++++++++++------ .../kubernetes/src/alpha/entityContents.tsx | 9 ++----- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index d22c2d94db..fe255b2ab1 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -189,15 +189,26 @@ function OutputLink(props: { }) { const routeRef = props.node?.instance?.getData(coreExtensionData.routeRef); - const link = useRouteRef(routeRef as RouteRef); + try { + const link = useRouteRef(routeRef as RouteRef); - return ( - {props.dataRef.id}}> - - {link ? link : null} - - - ); + return ( + {props.dataRef.id}}> + + {link ? link : null} + + + ); + } catch (ex) { + // eslint-disable-next-line no-console + console.warn( + props.node?.spec.id + ? `Unable to generate output link for ${props.node.spec.id}` + : 'Unable to generate output link', + ex, + ); + return null; + } } function Output(props: { dataRef: ExtensionDataRef; node?: AppNode }) { diff --git a/plugins/kubernetes/src/alpha/entityContents.tsx b/plugins/kubernetes/src/alpha/entityContents.tsx index de11310981..26730e76d4 100644 --- a/plugins/kubernetes/src/alpha/entityContents.tsx +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -15,19 +15,14 @@ */ import React from 'react'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; -import { rootCatalogKubernetesRouteRef } from '../plugin'; +import { compatWrapper } from '@backstage/core-compat-api'; import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; export const entityKubernetesContent = EntityContentBlueprint.make({ name: 'kubernetes', params: { - defaultPath: 'kubernetes', + defaultPath: '/kubernetes', defaultTitle: 'Kubernetes', - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), loader: () => import('./KubernetesContentPage').then(m => compatWrapper(), From 8753a47f5eecd591a6fe2fb2ec8fe40a0fb45f8a Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 19 Aug 2024 12:10:18 +0200 Subject: [PATCH 348/393] chore: added deprecation for `createComponentExtension` Signed-off-by: blam --- .../src/extensions/createComponentExtension.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index a40ee3b069..b83b2586ba 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -34,7 +34,9 @@ export function createComponentExtension< ref: ComponentRef; name?: string; disabled?: boolean; + /** @deprecated these will be removed in the future */ inputs?: TInputs; + /** @deprecated these will be removed in the future */ configSchema?: PortableSchema; loader: | { From e493020777ba39d3009c7dfc9b1e7a36ccc6886d Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 19 Aug 2024 12:12:21 +0200 Subject: [PATCH 349/393] chore: added chantgeset Signed-off-by: blam --- .changeset/eight-days-sin.md | 5 +++++ .changeset/swift-dragons-know.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/eight-days-sin.md create mode 100644 .changeset/swift-dragons-know.md diff --git a/.changeset/eight-days-sin.md b/.changeset/eight-days-sin.md new file mode 100644 index 0000000000..71b87ee93d --- /dev/null +++ b/.changeset/eight-days-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Deprecated `inputs` and `configSchema` options for `createComponentExtenion`, these will be removed in a future release diff --git a/.changeset/swift-dragons-know.md b/.changeset/swift-dragons-know.md new file mode 100644 index 0000000000..7e82994eca --- /dev/null +++ b/.changeset/swift-dragons-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': patch +--- + +Fixing issue with the visualizer crashing when clicking on the detailed view because of `routeRef` parameters From 163ba0826f896940802177939255cd2db4029f65 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 10:17:39 +0200 Subject: [PATCH 350/393] catalog-backend: Deprecate createRouter and CatalogBuilder Signed-off-by: Johan Haals --- .changeset/flat-bugs-drive.md | 5 +++++ plugins/catalog-backend/api-report.md | 11 ++++++----- .../catalog-backend/src/service/CatalogBuilder.ts | 12 ++++++++---- plugins/catalog-backend/src/service/createRouter.ts | 2 ++ 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 .changeset/flat-bugs-drive.md diff --git a/.changeset/flat-bugs-drive.md b/.changeset/flat-bugs-drive.md new file mode 100644 index 0000000000..4bf460c148 --- /dev/null +++ b/.changeset/flat-bugs-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Deprecated `createRouter`, `RouterOptions`, `CatalogBuilder` and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 7efa8669e6..1433cd51ca 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -26,6 +26,7 @@ import { CatalogProcessorRefreshKeysResult as CatalogProcessorRefreshKeysResult_ import { CatalogProcessorRelationResult as CatalogProcessorRelationResult_2 } from '@backstage/plugin-catalog-node'; import { CatalogProcessorResult as CatalogProcessorResult_2 } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import { DefaultCatalogCollatorFactory as DefaultCatalogCollatorFactory_2 } from '@backstage/plugin-search-backend-module-catalog'; import { DefaultCatalogCollatorFactoryOptions as DefaultCatalogCollatorFactoryOptions_2 } from '@backstage/plugin-search-backend-module-catalog'; import { DeferredEntity as DeferredEntity_2 } from '@backstage/plugin-catalog-node'; @@ -56,9 +57,9 @@ import { PlaceholderResolver as PlaceholderResolver_2 } from '@backstage/plugin- import { PlaceholderResolverParams as PlaceholderResolverParams_2 } from '@backstage/plugin-catalog-node'; import { PlaceholderResolverRead as PlaceholderResolverRead_2 } from '@backstage/plugin-catalog-node'; import { PlaceholderResolverResolveUrl as PlaceholderResolverResolveUrl_2 } from '@backstage/plugin-catalog-node'; -import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node'; @@ -137,7 +138,7 @@ export const CATALOG_CONFLICTS_TOPIC = 'experimental.catalog.conflict'; // @public (undocumented) export const CATALOG_ERRORS_TOPIC = 'experimental.catalog.errors'; -// @public +// @public @deprecated export class CatalogBuilder { addEntityPolicy( ...policies: Array> @@ -192,11 +193,11 @@ export class CatalogBuilder { export type CatalogCollatorEntityTransformer = CatalogCollatorEntityTransformer_2; -// @public (undocumented) +// @public @deprecated (undocumented) export type CatalogEnvironment = { logger: LoggerService; - database: PluginDatabaseManager; - config: Config; + database: DatabaseService; + config: RootConfigService; reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; scheduler?: PluginTaskScheduler; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 344c231f7c..368f8ae731 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -17,7 +17,6 @@ import { createLegacyAuthAdapters, HostDiscovery, - PluginDatabaseManager, } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { @@ -107,10 +106,12 @@ import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { durationToMilliseconds } from '@backstage/types'; import { AuthService, + DatabaseService, DiscoveryService, HttpAuthService, LoggerService, PermissionsService, + RootConfigService, UrlReaderService, } from '@backstage/backend-plugin-api'; @@ -123,11 +124,13 @@ export type CatalogPermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, > = PermissionRule; -/** @public */ +/** + * @deprecated Please migrate to the new backend system as this will be removed in the future. + * @public */ export type CatalogEnvironment = { logger: LoggerService; - database: PluginDatabaseManager; - config: Config; + database: DatabaseService; + config: RootConfigService; reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; scheduler?: PluginTaskScheduler; @@ -160,6 +163,7 @@ export type CatalogEnvironment = { * persisted in the catalog. * * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export class CatalogBuilder { private readonly env: CatalogEnvironment; diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 774ee61a6d..7c42814686 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -60,6 +60,7 @@ import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; * Options used by {@link createRouter}. * * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; @@ -79,6 +80,7 @@ export interface RouterOptions { * Creates a catalog router. * * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export async function createRouter( options: RouterOptions, From ae3c730296ea83d8ff8ac678a5667b5ef4972804 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 13:04:29 +0200 Subject: [PATCH 351/393] Update plugins/catalog-backend/src/service/createRouter.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals Signed-off-by: Johan Haals --- plugins/catalog-backend/src/service/createRouter.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 7c42814686..776cabce17 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -78,9 +78,6 @@ export interface RouterOptions { /** * Creates a catalog router. - * - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export async function createRouter( options: RouterOptions, From 18c281a859da0b7feec6578122db87dfcf652af9 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 13:04:45 +0200 Subject: [PATCH 352/393] Update plugins/catalog-backend/src/service/CatalogBuilder.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals Signed-off-by: Johan Haals --- plugins/catalog-backend/src/service/CatalogBuilder.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 368f8ae731..585ed272e7 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -126,7 +126,8 @@ export type CatalogPermissionRuleInput< /** * @deprecated Please migrate to the new backend system as this will be removed in the future. - * @public */ + * @public + */ export type CatalogEnvironment = { logger: LoggerService; database: DatabaseService; From 350e44e483880c7dc8e637b9d353391c50c0d776 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 13:05:03 +0200 Subject: [PATCH 353/393] Update .changeset/flat-bugs-drive.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals Signed-off-by: Johan Haals --- .changeset/flat-bugs-drive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/flat-bugs-drive.md b/.changeset/flat-bugs-drive.md index 4bf460c148..dcdc65496f 100644 --- a/.changeset/flat-bugs-drive.md +++ b/.changeset/flat-bugs-drive.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': minor --- -Deprecated `createRouter`, `RouterOptions`, `CatalogBuilder` and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. +Deprecated `RouterOptions`, `CatalogBuilder`, and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. From 9342ac8e4e5940dca18fd39eebc85349553eaa0f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 14:54:40 +0200 Subject: [PATCH 354/393] chore: remove unused dependency on backend-common Signed-off-by: Johan Haals --- .changeset/dry-hotels-film.md | 12 ++++++++++++ plugins/catalog-backend-module-aws/package.json | 1 - plugins/catalog-backend-module-azure/package.json | 1 - .../package.json | 1 - plugins/catalog-backend-module-gcp/package.json | 1 - plugins/catalog-backend-module-gerrit/package.json | 1 - .../catalog-backend-module-github-org/package.json | 1 - plugins/catalog-backend-module-msgraph/package.json | 2 -- plugins/catalog-backend-module-puppetdb/package.json | 1 - yarn.lock | 8 -------- 10 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 .changeset/dry-hotels-film.md diff --git a/.changeset/dry-hotels-film.md b/.changeset/dry-hotels-film.md new file mode 100644 index 0000000000..66e2152eb2 --- /dev/null +++ b/.changeset/dry-hotels-film.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-github-org': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-gcp': patch +--- + +Removed unused dependency diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index deff27597b..d3a5a19a9b 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -57,7 +57,6 @@ "@aws-sdk/credential-providers": "^3.350.0", "@aws-sdk/middleware-endpoint": "^3.347.0", "@aws-sdk/types": "^3.347.0", - "@backstage/backend-common": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 42e7f7c927..622be8707b 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -51,7 +51,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 8ec612797c..55bc4a3f76 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -50,7 +50,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 55392c01a1..39f875ecea 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -51,7 +51,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 4510d5571a..85e927de83 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -47,7 +47,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 79722394cb..969f985e5e 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -33,7 +33,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index afeeaeb6b3..5275613e16 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -52,7 +52,6 @@ }, "dependencies": { "@azure/identity": "^4.0.0", - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", @@ -68,7 +67,6 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 366063e5ca..94b6bcf591 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -53,7 +53,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/yarn.lock b/yarn.lock index f20870e0c4..5c13778196 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5290,7 +5290,6 @@ __metadata: "@aws-sdk/middleware-endpoint": ^3.347.0 "@aws-sdk/types": ^3.347.0 "@aws-sdk/util-stream-node": ^3.350.0 - "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" @@ -5318,7 +5317,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-azure@workspace:plugins/catalog-backend-module-azure" dependencies: - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -5382,7 +5380,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-bitbucket-server@workspace:plugins/catalog-backend-module-bitbucket-server" dependencies: - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -5404,7 +5401,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-gcp@workspace:plugins/catalog-backend-module-gcp" dependencies: - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -5421,7 +5417,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-gerrit@workspace:plugins/catalog-backend-module-gerrit" dependencies: - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -5443,7 +5438,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-github-org@workspace:plugins/catalog-backend-module-github-org" dependencies: - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -5596,7 +5590,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-msgraph@workspace:plugins/catalog-backend-module-msgraph" dependencies: "@azure/identity": ^4.0.0 - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -5644,7 +5637,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-puppetdb@workspace:plugins/catalog-backend-module-puppetdb" dependencies: - "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From d6f0d712a5450e90bf5c6c377049f27e588f23b3 Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Mon, 19 Aug 2024 09:40:45 -0400 Subject: [PATCH 355/393] update other scaffolder inputs to textfield Signed-off-by: Stephen Glass --- .changeset/clever-laws-raise.md | 3 +- .../PasswordWidget/PasswordWidget.tsx | 7 ++--- .../DefaultRepoBranchPicker.tsx | 7 ++--- .../fields/RepoUrlPicker/AzureRepoPicker.tsx | 31 ++++++++----------- .../fields/RepoUrlPicker/GerritRepoPicker.tsx | 11 +++---- .../fields/RepoUrlPicker/GiteaRepoPicker.tsx | 7 ++--- .../fields/RepoUrlPicker/GithubRepoPicker.tsx | 17 +++++----- .../fields/RepoUrlPicker/GitlabRepoPicker.tsx | 17 +++++----- 8 files changed, 43 insertions(+), 57 deletions(-) diff --git a/.changeset/clever-laws-raise.md b/.changeset/clever-laws-raise.md index 12879621a4..8733642cc8 100644 --- a/.changeset/clever-laws-raise.md +++ b/.changeset/clever-laws-raise.md @@ -1,5 +1,6 @@ --- +'@backstage/plugin-scaffolder': patch '@backstage/plugin-scaffolder-react': patch --- -Change scaffolder secret widget to use `TextField` component for more flexibility in theme overrides. +Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. diff --git a/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx b/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx index 6c1c2c176f..1807df8ae2 100644 --- a/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx +++ b/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx @@ -15,8 +15,7 @@ */ import { WidgetProps } from '@rjsf/utils'; -import InputLabel from '@material-ui/core/InputLabel'; -import Input from '@material-ui/core/Input'; +import TextField from '@material-ui/core/TextField'; import React from 'react'; import FormHelperText from '@material-ui/core/FormHelperText'; import { MarkdownContent } from '@backstage/core-components'; @@ -32,9 +31,9 @@ export const PasswordWidget = ( return ( <> - {title} - { onChange(e.target.value); diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx index 9350e232f8..0fecfb7947 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx @@ -17,8 +17,7 @@ import FormControl from '@material-ui/core/FormControl'; import React from 'react'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { BaseRepoBranchPickerProps } from './types'; @@ -43,9 +42,9 @@ export const DefaultRepoBranchPicker = ({ required={required} error={rawErrors?.length > 0 && !branch} > - Branch - onChange({ branch: e.target.value })} value={branch} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx index 48fcb4f6af..c7b0f1f887 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -17,8 +17,7 @@ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { BaseRepoUrlPickerProps } from './types'; import { Select, SelectItem } from '@backstage/core-components'; @@ -65,14 +64,12 @@ export const AzureRepoPicker = ( items={organizationItems} /> ) : ( - <> - Organization - onChange({ organization: e.target.value })} - value={organization} - /> - + onChange({ organization: e.target.value })} + value={organization} + /> )} The Organization that this repo will belong to @@ -95,14 +92,12 @@ export const AzureRepoPicker = ( items={projectItems} /> ) : ( - <> - Project - onChange({ project: e.target.value })} - value={project} - /> - + onChange({ project: e.target.value })} + value={project} + /> )} The Project that this repo will belong to diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx index 1b13c5eb29..48504a0f7c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx @@ -16,8 +16,7 @@ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { BaseRepoUrlPickerProps } from './types'; export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => { @@ -26,9 +25,9 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => { return ( <> 0 && !workspace}> - Owner - onChange({ owner: e.target.value })} value={owner} /> @@ -39,9 +38,9 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => { required error={rawErrors?.length > 0 && !workspace} > - Parent - onChange({ workspace: e.target.value })} value={workspace} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx index 9fb3af211f..206ace11ef 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx @@ -16,8 +16,7 @@ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { Select, SelectItem } from '@backstage/core-components'; import { BaseRepoUrlPickerProps } from './types'; @@ -56,9 +55,9 @@ export const GiteaRepoPicker = ( /> ) : ( <> - Owner - onChange({ owner: e.target.value })} value={owner} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx index 5d46dc4333..fc40be6670 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx @@ -16,8 +16,7 @@ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { Select, SelectItem } from '@backstage/core-components'; import { BaseRepoUrlPickerProps } from './types'; @@ -52,14 +51,12 @@ export const GithubRepoPicker = ( items={ownerItems} /> ) : ( - <> - Owner - onChange({ owner: e.target.value })} - value={owner} - /> - + onChange({ owner: e.target.value })} + value={owner} + /> )} The organization, user or project that this repo will belong to diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx index 12ae37bf15..f13d4bbbf3 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx @@ -16,8 +16,7 @@ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { Select, SelectItem } from '@backstage/core-components'; import { BaseRepoUrlPickerProps } from './types'; @@ -55,14 +54,12 @@ export const GitlabRepoPicker = ( items={ownerItems} /> ) : ( - <> - Owner - onChange({ owner: e.target.value })} - value={owner} - /> - + onChange({ owner: e.target.value })} + value={owner} + /> )} GitLab namespace where this repository will belong to. It can be the From 2886ef7eb44e2c680730a68bdd1da8dc7a4175da Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 19 Aug 2024 15:45:24 +0200 Subject: [PATCH 356/393] devtools-backend: deprecate createRouter Signed-off-by: Johan Haals --- .changeset/two-crabs-call.md | 5 +++++ plugins/devtools-backend/api-report.md | 7 ++++--- plugins/devtools-backend/src/service/router.ts | 14 ++++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 .changeset/two-crabs-call.md diff --git a/.changeset/two-crabs-call.md b/.changeset/two-crabs-call.md new file mode 100644 index 0000000000..8f6979fac4 --- /dev/null +++ b/.changeset/two-crabs-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-backend': patch +--- + +Deprecated `createRouter` and its router options in favour of the new backend system. diff --git a/plugins/devtools-backend/api-report.md b/plugins/devtools-backend/api-report.md index 482422e66f..835eb23e26 100644 --- a/plugins/devtools-backend/api-report.md +++ b/plugins/devtools-backend/api-report.md @@ -13,8 +13,9 @@ import { ExternalDependency } from '@backstage/plugin-devtools-common'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionsService } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; -// @public (undocumented) +// @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; // @public (undocumented) @@ -32,10 +33,10 @@ export class DevToolsBackendApi { const devtoolsPlugin: BackendFeatureCompat; export default devtoolsPlugin; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) devToolsBackendApi?: DevToolsBackendApi; // (undocumented) diff --git a/plugins/devtools-backend/src/service/router.ts b/plugins/devtools-backend/src/service/router.ts index cec3d87a5a..e3eb72c293 100644 --- a/plugins/devtools-backend/src/service/router.ts +++ b/plugins/devtools-backend/src/service/router.ts @@ -21,7 +21,6 @@ import { devToolsPermissions, } from '@backstage/plugin-devtools-common'; -import { Config } from '@backstage/config'; import { DevToolsBackendApi } from '../api'; import { NotAllowedError } from '@backstage/errors'; import Router from 'express-promise-router'; @@ -36,19 +35,26 @@ import { HttpAuthService, LoggerService, PermissionsService, + RootConfigService, } from '@backstage/backend-plugin-api'; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { devToolsBackendApi?: DevToolsBackendApi; logger: LoggerService; - config: Config; + config: RootConfigService; permissions: PermissionsService; discovery: DiscoveryService; httpAuth?: HttpAuthService; } -/** @public */ +/** + * @deprecated Please migrate to the new backend system as this will be removed in the future. + * @public + * */ export async function createRouter( options: RouterOptions, ): Promise { From fc24d9ebf042859ec437fd654f4d91ddb915e59b Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 16 Aug 2024 13:59:40 +0200 Subject: [PATCH 357/393] refactor: stop using backend-tasks in packages and plugins Signed-off-by: Camila Belo --- .changeset/gentle-radios-cheat.md | 30 +++++++++++++++++ .changeset/soft-files-greet.md | 16 +++++++++ .../architecture/05-extension-points.md | 2 +- docs/features/search/collators.md | 4 +-- docs/features/search/concepts.md | 2 +- docs/features/search/getting-started.md | 13 +++++--- docs/integrations/aws-s3/discovery--old.md | 4 +-- docs/integrations/aws-s3/discovery.md | 4 +-- docs/integrations/azure/discovery--old.md | 2 +- docs/integrations/azure/discovery.md | 2 +- docs/integrations/bitbucketCloud/discovery.md | 2 +- .../integrations/bitbucketServer/discovery.md | 2 +- docs/integrations/github/discovery--old.md | 4 +-- docs/integrations/github/discovery.md | 4 +-- docs/integrations/github/org.md | 2 +- docs/integrations/gitlab/discovery.md | 2 +- docs/integrations/gitlab/org.md | 2 +- docs/overview/threat-model.md | 2 +- packages/backend-app-api/package.json | 1 - .../api-report.md | 8 ++--- .../package.json | 1 - .../src/manager/types.ts | 7 ++-- packages/backend-legacy/package.json | 1 - packages/backend-legacy/src/index.ts | 8 +++-- packages/backend-legacy/src/types.ts | 8 +++-- packages/backend-tasks/README.md | 16 ++++----- packages/backend-tasks/package.json | 33 ++++++++++--------- packages/backend-tasks/src/index.ts | 4 +++ packages/backend/package.json | 1 - .../catalog-backend-module-aws/api-report.md | 8 ++--- .../catalog-backend-module-aws/config.d.ts | 6 ++-- .../catalog-backend-module-aws/package.json | 1 - .../catalogModuleAwsS3EntityProvider.test.ts | 4 +-- .../src/providers/AwsS3EntityProvider.test.ts | 18 +++++----- .../src/providers/AwsS3EntityProvider.ts | 17 ++++++---- .../src/providers/config.ts | 6 ++-- .../src/providers/types.ts | 4 +-- .../api-report.md | 8 ++--- .../catalog-backend-module-azure/config.d.ts | 4 +-- .../catalog-backend-module-azure/package.json | 1 - ...logModuleAzureDevOpsEntityProvider.test.ts | 4 +-- .../AzureDevOpsEntityProvider.test.ts | 18 +++++----- .../providers/AzureDevOpsEntityProvider.ts | 17 ++++++---- .../src/providers/config.ts | 6 ++-- .../src/providers/types.ts | 4 +-- .../package.json | 1 - .../InternalOpenApiDocumentationProvider.ts | 11 ++++--- .../api-report.md | 8 ++--- .../config.d.ts | 6 ++-- .../package.json | 1 - ...ModuleBitbucketCloudEntityProvider.test.ts | 8 +++-- .../BitbucketCloudEntityProvider.test.ts | 18 +++++----- .../providers/BitbucketCloudEntityProvider.ts | 17 ++++++---- .../BitbucketCloudEntityProviderConfig.ts | 12 ++++--- .../api-report.md | 8 ++--- .../config.d.ts | 6 ++-- .../package.json | 1 - ...oduleBitbucketServerEntityProvider.test.ts | 4 +-- .../BitbucketServerEntityProvider.test.ts | 18 +++++----- .../BitbucketServerEntityProvider.ts | 17 ++++++---- .../BitbucketServerEntityProviderConfig.ts | 12 ++++--- .../catalog-backend-module-gcp/config.d.ts | 4 +-- .../catalog-backend-module-gcp/package.json | 1 - .../src/providers/GkeEntityProvider.test.ts | 4 +-- .../src/providers/GkeEntityProvider.ts | 19 ++++++----- .../api-report.md | 8 ++--- .../package.json | 1 - .../catalogModuleGerritEntityProvider.test.ts | 4 +-- .../providers/GerritEntityProvider.test.ts | 18 +++++----- .../src/providers/GerritEntityProvider.ts | 17 ++++++---- .../src/providers/config.ts | 6 ++-- .../src/providers/types.ts | 4 +-- .../package.json | 1 - .../src/module.test.ts | 4 +-- .../src/module.ts | 12 +++---- .../api-report.md | 16 ++++----- .../catalog-backend-module-github/config.d.ts | 10 +++--- .../package.json | 1 - .../src/deprecated.ts | 11 ++++--- .../src/module/githubCatalogModule.test.ts | 4 +-- .../providers/GithubEntityProvider.test.ts | 18 +++++----- .../src/providers/GithubEntityProvider.ts | 17 ++++++---- .../providers/GithubEntityProviderConfig.ts | 12 ++++--- .../providers/GithubMultiOrgEntityProvider.ts | 10 +++--- .../src/providers/GithubOrgEntityProvider.ts | 6 ++-- .../package.json | 1 - ...leGitlabOrgDiscoveryEntityProvider.test.ts | 8 +++-- .../api-report.md | 16 ++++----- .../catalog-backend-module-gitlab/config.d.ts | 4 +-- .../package.json | 1 - .../src/lib/types.ts | 4 +-- ...oduleGitlabDiscoveryEntityProvider.test.ts | 8 +++-- .../GitlabDiscoveryEntityProvider.test.ts | 16 ++++----- .../GitlabDiscoveryEntityProvider.ts | 17 ++++++---- .../GitlabOrgDiscoveryEntityProvider.test.ts | 18 +++++----- .../GitlabOrgDiscoveryEntityProvider.ts | 17 ++++++---- .../src/providers/config.ts | 6 ++-- .../api-report.md | 4 +-- .../package.json | 1 - .../src/module/WrapperProviders.test.ts | 5 ++- .../src/types.ts | 15 +++++---- .../catalog-backend-module-ldap/package.json | 1 - .../api-report.md | 14 ++++---- .../config.d.ts | 6 ++-- .../package.json | 1 - .../src/microsoftGraph/config.ts | 12 ++++--- ...uleMicrosoftGraphOrgEntityProvider.test.ts | 4 +-- .../MicrosoftGraphOrgEntityProvider.test.ts | 16 ++++----- .../MicrosoftGraphOrgEntityProvider.ts | 19 ++++++----- .../api-report.md | 12 +++---- .../config.d.ts | 6 ++-- .../package.json | 1 - ...atalogModulePuppetDbEntityProvider.test.ts | 4 +-- .../providers/PuppetDbEntityProvider.test.ts | 11 ++++--- .../src/providers/PuppetDbEntityProvider.ts | 21 +++++++----- .../providers/PuppetDbEntityProviderConfig.ts | 12 ++++--- plugins/catalog-backend/api-report.md | 4 +-- plugins/catalog-backend/package.json | 1 - .../DefaultCatalogProcessingEngine.ts | 7 ++-- .../src/service/CatalogBuilder.ts | 4 +-- .../src/service/createRouter.ts | 4 +-- .../api-report.md | 4 +-- .../package.json | 1 - .../AwsSqsConsumingEventPublisher.test.ts | 8 ++--- .../AwsSqsConsumingEventPublisher.ts | 6 ++-- .../src/publisher/config.ts | 2 +- plugins/scaffolder-backend/api-report.md | 4 +-- plugins/scaffolder-backend/package.json | 1 - .../scaffolder-backend/src/service/router.ts | 4 +-- .../search-backend-module-catalog/config.d.ts | 4 +-- .../package.json | 1 - .../src/collators/config.ts | 13 ++++---- .../search-backend-module-explore/config.d.ts | 4 +-- .../package.json | 1 - .../src/alpha.ts | 4 +-- .../package.json | 1 - .../SearchStackOverflowCollatorModule.ts | 4 +-- .../config.d.ts | 4 +-- .../package.json | 1 - .../src/alpha.ts | 4 +-- plugins/search-backend-node/api-report.md | 10 +++--- plugins/search-backend-node/package.json | 1 - .../src/IndexBuilder.test.ts | 11 +++++-- plugins/search-backend-node/src/Scheduler.ts | 15 +++++---- plugins/search-backend-node/src/types.ts | 6 ++-- yarn.lock | 29 +--------------- 146 files changed, 605 insertions(+), 508 deletions(-) create mode 100644 .changeset/gentle-radios-cheat.md create mode 100644 .changeset/soft-files-greet.md diff --git a/.changeset/gentle-radios-cheat.md b/.changeset/gentle-radios-cheat.md new file mode 100644 index 0000000000..9fcaef11d3 --- /dev/null +++ b/.changeset/gentle-radios-cheat.md @@ -0,0 +1,30 @@ +--- +'@backstage/frontend-app-api': minor +'@backstage/backend-app-api': minor +'@backstage/backend-dynamic-feature-service': minor +'@backstage/plugin-catalog-backend-module-aws': minor +'@backstage/plugin-catalog-backend-module-azure': minor +'@backstage/plugin-catalog-backend-module-backstage-openapi': minor +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor +'@backstage/plugin-catalog-backend-module-bitbucket-server': minor +'@backstage/plugin-catalog-backend-module-gcp': minor +'@backstage/plugin-catalog-backend-module-gerrit': minor +'@backstage/plugin-catalog-backend-module-github-org': minor +'@backstage/plugin-catalog-backend-module-github': minor +'@backstage/plugin-catalog-backend-module-gitlab-org': minor +'@backstage/plugin-catalog-backend-module-gitlab': minor +'@backstage/plugin-catalog-backend-module-incremental-ingestion': minor +'@backstage/plugin-catalog-backend-module-ldap': minor +'@backstage/plugin-catalog-backend-module-msgraph': minor +'@backstage/plugin-catalog-backend-module-puppetdb': minor +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-events-backend-module-aws-sqs': minor +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-search-backend-module-catalog': minor +'@backstage/plugin-search-backend-module-explore': minor +'@backstage/plugin-search-backend-module-stack-overflow-collator': minor +'@backstage/plugin-search-backend-module-techdocs': minor +'@backstage/plugin-search-backend-node': minor +--- + +Stop using `@backstage/backend-tasks` as it will be deleted in near future. diff --git a/.changeset/soft-files-greet.md b/.changeset/soft-files-greet.md new file mode 100644 index 0000000000..3d83790f0a --- /dev/null +++ b/.changeset/soft-files-greet.md @@ -0,0 +1,16 @@ +--- +'@backstage/backend-tasks': minor +--- + +This package is deprecated and will be removed in a near future, follow the instructions below to stop using it: + +- `TaskScheduler`: Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults; +- `TaskRunner`: Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead; +- `TaskFunction`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; +- `TaskDescriptor`: Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead; +- `TaskInvocationDefinition`: Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead; +- `TaskScheduleDefinition`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; +- `TaskScheduleDefinitionConfig`: Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead; +- `PluginTaskScheduler`: Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`); +- `readTaskScheduleDefinitionFromConfig`: Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead; +- `HumanDuration`: Import `TypesHumanDuration` from `@backstage/types` instead. diff --git a/docs/backend-system/architecture/05-extension-points.md b/docs/backend-system/architecture/05-extension-points.md index 4b7f893f0b..12c4ec75f5 100644 --- a/docs/backend-system/architecture/05-extension-points.md +++ b/docs/backend-system/architecture/05-extension-points.md @@ -78,7 +78,7 @@ Another pattern that can be used is a type of singleton pattern where the extens ```ts interface ScaffolderTaskRunnerExtensionPoint { - setTaskRunner(taskRunner: TaskRunner): void; + setTaskRunner(taskRunner: SchedulerServiceTaskRunner): void; } ``` diff --git a/docs/features/search/collators.md b/docs/features/search/collators.md index 4054ec1212..27860bfd64 100644 --- a/docs/features/search/collators.md +++ b/docs/features/search/collators.md @@ -41,7 +41,7 @@ The default schedule for the Catalog Collator is to run every 10 minutes, you ca search: collators: catalog: - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code initialDelay: { seconds: 90 } # supports cron, ISO duration, "human duration" as used in code @@ -85,7 +85,7 @@ The default schedule for the TechDocs Collator is to run every 10 minutes, you c search: collators: techdocs: - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code initialDelay: { seconds: 90 } # supports cron, ISO duration, "human duration" as used in code diff --git a/docs/features/search/concepts.md b/docs/features/search/concepts.md index 69a7223085..a777c2f53c 100644 --- a/docs/features/search/concepts.md +++ b/docs/features/search/concepts.md @@ -86,7 +86,7 @@ Search chooses to completely rebuild indices on a schedule. Different collators can be configured to refresh at different intervals, depending on how often the source information is updated. When search indexing is distributed among multiple backend nodes, coordination to prevent clashes is typically handled by a -distributed `TaskRunner`. +distributed `SchedulerServiceTaskRunner`. ### The Search Page diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index c2ca2e1dce..362b7fc46d 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -283,7 +283,7 @@ Backstage Search builds and maintains its index [on a schedule](./concepts.md#the-scheduler). You can change how often the indexes are rebuilt for a given type of document. You may want to do this if your documents are updated more or less frequently. You can do so by configuring -a scheduled `TaskRunner` to pass into the `schedule` value, like this: +a scheduled `SchedulerServiceTaskRunner` to pass into the `schedule` value, like this: ```typescript {3} const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ @@ -302,16 +302,19 @@ indexBuilder.addCollator({ ``` Note: if you are using the in-memory Lunr search engine, you probably want to -implement a non-distributed `TaskRunner` like the following to ensure consistency +implement a non-distributed `SchedulerServiceTaskRunner` like the following to ensure consistency if you're running multiple search backend nodes (alternatively, you can configure the search plugin to use a non-distributed database such as [SQLite](../../tutorials/configuring-plugin-databases.md#postgresql-and-sqlite-3)): ```typescript -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; -const schedule: TaskRunner = { - run: async (task: TaskInvocationDefinition) => { +const schedule: SchedulerServiceTaskRunner = { + run: async (task: SchedulerServiceTaskInvocationDefinition) => { const startRefresh = async () => { while (!task.signal?.aborted) { try { diff --git a/docs/integrations/aws-s3/discovery--old.md b/docs/integrations/aws-s3/discovery--old.md index 09b6eb6cc0..3a9d30a370 100644 --- a/docs/integrations/aws-s3/discovery--old.md +++ b/docs/integrations/aws-s3/discovery--old.md @@ -36,7 +36,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -56,7 +56,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index db9119680e..4cca448cf2 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -36,7 +36,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -56,7 +56,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/azure/discovery--old.md b/docs/integrations/azure/discovery--old.md index 56a2fe559f..27532fe6ee 100644 --- a/docs/integrations/azure/discovery--old.md +++ b/docs/integrations/azure/discovery--old.md @@ -45,7 +45,7 @@ catalog: project: myproject repository: service-* # this will match all repos starting with service-* path: /catalog-info.yaml - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # optional; same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index c8a8a3af7c..357df7d553 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -45,7 +45,7 @@ catalog: project: myproject repository: service-* # this will match all repos starting with service-* path: /catalog-info.yaml - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # optional; same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 04dc111d30..6ba9c0aa1f 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -146,7 +146,7 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 99e04affa8..133978a8fb 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -63,7 +63,7 @@ catalog: projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp skipArchivedRepos: true # optional; boolean - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/github/discovery--old.md b/docs/integrations/github/discovery--old.md index 752aa51bde..901462b1cb 100644 --- a/docs/integrations/github/discovery--old.md +++ b/docs/integrations/github/discovery--old.md @@ -133,7 +133,7 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -249,7 +249,7 @@ schedule: timeout: { minutes: 3 } ``` -More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page. +More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page. Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication which carries a much higher rate limit at GitHub. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 43e63a618c..b2e998813d 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -83,7 +83,7 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -203,7 +203,7 @@ schedule: timeout: { minutes: 3 } ``` -More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page. +More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page. Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication which carries a much higher rate limit at GitHub. diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 18865cd630..61e42567a9 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -94,7 +94,7 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru - `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts. - `githubUrl`: The target that this provider should consume - `orgs` (optional): The list of the GitHub orgs to consume. If you only list a single org the generated group entities will use the `default` namespace, otherwise they will use the org name as the namespace. By default the provider will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). -- `schedule`: The refresh schedule to use, matches the structure of [`TaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinitionconfig/) +- `schedule`: The refresh schedule to use, matches the structure of [`SchedulerServiceTaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinitionconfig/) ### Events Support diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 70f12f181e..79bd37061a 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -154,7 +154,7 @@ catalog: entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided patter. Defaults to `[\s\S]*`, which means to not filter anything excludeRepos: [] # Optional. A list of project paths that should be excluded from discovery, e.g. group/subgroup/repo. Should not start or end with a slash. - schedule: # Same options as in TaskScheduleDefinition. Optional for the Legacy Backend System + schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 65f66aa763..24b64d1116 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -173,7 +173,7 @@ catalog: group: org/teams # Required for gitlab.com when `orgEnabled: true`. Optional for self managed. Must not end with slash. Accepts only groups under the provided path (which will be stripped) allowInherited: true # Allow groups to be ingested even if there are no direct members. groupPattern: '[\s\S]*' # Optional. Filters found groups based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything - schedule: # Same options as in TaskScheduleDefinition. Optional for the Legacy Backend System. + schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System. # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index c0e2d61e25..74c055a86c 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -41,7 +41,7 @@ The built-in protection against unauthorized access does not by default include ## Common Backend Configuration -There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `TaskScheduler` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected. +There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `SchedulerService` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected. In a typical Backstage setup, there is no boundary between plugins that run on the same host. Likewise, there is no boundary between plugins that share the same database access. Any plugin that is running on a host that has access to the logical database of any other plugin should be considered to have full access to that other plugin. For example, even if you deploy the `auth` and `catalog` plugins on separate hosts with separate configuration and credentials, the `catalog` plugin is still considered to have full access to the `auth` plugin as long as the `catalog` plugin has access to the `auth` plugin's logical database. The only way to create a boundary between the two plugins is to deploy them in such a way that they do not have access to each others’ database. This applies to the database facility as well as any other shared resources, such as the cache. diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 588a969390..e5a80cc1eb 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -52,7 +52,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 400c55c009..d7a4b8b75d 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -25,12 +25,12 @@ import { PermissionPolicy } from '@backstage/plugin-permission-node'; import { PluginCacheManager } from '@backstage/backend-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; -import { TaskRunner } from '@backstage/backend-tasks'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { TokenManager } from '@backstage/backend-common'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -210,7 +210,7 @@ export interface LegacyBackendPluginInstaller { // (undocumented) search?( indexBuilder: IndexBuilder, - schedule: TaskRunner, + schedule: SchedulerServiceTaskRunner, env: LegacyPluginEnvironment, ): void; } @@ -225,7 +225,7 @@ export type LegacyPluginEnvironment = { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; identity: IdentityApi; eventBroker: EventBroker; events: EventsService; diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 56474e4a65..b977fe5756 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -49,7 +49,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", diff --git a/packages/backend-dynamic-feature-service/src/manager/types.ts b/packages/backend-dynamic-feature-service/src/manager/types.ts index 0234a8b66e..a4f2937fd5 100644 --- a/packages/backend-dynamic-feature-service/src/manager/types.ts +++ b/packages/backend-dynamic-feature-service/src/manager/types.ts @@ -23,7 +23,6 @@ import { TokenManager, } from '@backstage/backend-common'; import { Router } from 'express'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -35,6 +34,8 @@ import { import { BackendFeature, UrlReaderService, + SchedulerService, + SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; import { PackagePlatform, PackageRole } from '@backstage/cli-node'; import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; @@ -64,7 +65,7 @@ export type LegacyPluginEnvironment = { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; identity: IdentityApi; eventBroker: EventBroker; events: EventsService; @@ -163,7 +164,7 @@ export interface LegacyBackendPluginInstaller { scaffolder?(env: LegacyPluginEnvironment): TemplateAction[]; search?( indexBuilder: IndexBuilder, - schedule: TaskRunner, + schedule: SchedulerServiceTaskRunner, env: LegacyPluginEnvironment, ): void; events?( diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 4fee66d925..446207959b 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -31,7 +31,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/packages/backend-legacy/src/index.ts b/packages/backend-legacy/src/index.ts index e422a67267..9a581e7d26 100644 --- a/packages/backend-legacy/src/index.ts +++ b/packages/backend-legacy/src/index.ts @@ -34,7 +34,6 @@ import { ServerTokenManager, useHotMemoize, } from '@backstage/backend-common'; -import { TaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import healthcheck from './plugins/healthcheck'; import { metricsHandler, metricsInit } from './metrics'; @@ -57,6 +56,7 @@ import { DefaultEventBroker } from '@backstage/plugin-events-backend'; import { DefaultEventsService } from '@backstage/plugin-events-node'; import { DefaultSignalsService } from '@backstage/plugin-signals-node'; import { UrlReaders } from '@backstage/backend-defaults/urlReader'; +import { DefaultSchedulerService } from '@backstage/backend-defaults/scheduler'; function makeCreateEnv(config: Config) { const root = getRootLogger(); @@ -69,7 +69,6 @@ function makeCreateEnv(config: Config) { }); const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); const cacheManager = CacheManager.fromConfig(config); - const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); const identity = DefaultIdentityClient.create({ discovery, }); @@ -89,7 +88,10 @@ function makeCreateEnv(config: Config) { const logger = root.child({ type: 'plugin', plugin }); const database = databaseManager.forPlugin(plugin); const cache = cacheManager.forPlugin(plugin); - const scheduler = taskScheduler.forPlugin(plugin); + const scheduler = DefaultSchedulerService.create({ + logger, + database, + }); return { logger, diff --git a/packages/backend-legacy/src/types.ts b/packages/backend-legacy/src/types.ts index b2c0f58ee9..3f5d163925 100644 --- a/packages/backend-legacy/src/types.ts +++ b/packages/backend-legacy/src/types.ts @@ -22,12 +22,14 @@ import { PluginEndpointDiscovery, TokenManager, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { SignalsService } from '@backstage/plugin-signals-node'; -import { UrlReaderService } from '@backstage/backend-plugin-api'; +import { + UrlReaderService, + SchedulerService, +} from '@backstage/backend-plugin-api'; export type PluginEnvironment = { logger: Logger; @@ -38,7 +40,7 @@ export type PluginEnvironment = { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; identity: IdentityApi; /** * @deprecated use `events` instead diff --git a/packages/backend-tasks/README.md b/packages/backend-tasks/README.md index 57cd63b0e0..bdb151dea6 100644 --- a/packages/backend-tasks/README.md +++ b/packages/backend-tasks/README.md @@ -1,9 +1,16 @@ # @backstage/backend-tasks +> [!CAUTION] +> This package is deprecated and will be removed in a near future. + Common distributed task management for Backstage backends. ## Usage +> [!CAUTION] +> Please note that the documentation below is only valid for versions equal to or below `0.5.28-next.3`. +> As this package will be deleted soon, we recommend that you migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults`. Here are the [backend](https://backstage.io/docs/backend-system/building-backends/migrating) and [plugin](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) migration guides. + Add the library to your backend package: ```bash @@ -27,12 +34,3 @@ await scheduler.scheduleTask({ }, }); ``` - -## Local Development - -When working with the `@backstage/backend-tasks` library you may run into your task not running immediately at startup as expected if you are using a persistent database. This is by design - the library respects the previous state and does not run the task sooner than the specified frequency. If you want to get around this, there is a table called `backstage_backend_tasks__tasks` in the applicable plugin's database which will contain a record with the next run date and time. You can delete this record to get things back to what you expect. - -## Documentation - -- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) -- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 8b815be190..7830df5bde 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,35 +1,39 @@ { "name": "@backstage/backend-tasks", - "description": "Common distributed task management library for Backstage backends", "version": "0.5.28-next.3", - "main": "src/index.ts", - "types": "src/index.ts", + "description": "Common distributed task management library for Backstage backends", + "backstage": { + "role": "node-library" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, - "backstage": { - "role": "node-library" - }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/backend-tasks" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "migrations/**/*.{js,d.ts}" + ], "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-common": "workspace:^", @@ -51,8 +55,5 @@ "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" }, - "files": [ - "dist", - "migrations/**/*.{js,d.ts}" - ] + "deprecated": true } diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts index 0aae81b143..83c7606700 100644 --- a/packages/backend-tasks/src/index.ts +++ b/packages/backend-tasks/src/index.ts @@ -17,6 +17,10 @@ /** * Common distributed task management library for Backstage backends * + * @remarks + * This package is deprecated and will be removed in a near future. + * Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults`. + * * @packageDocumentation */ diff --git a/packages/backend/package.json b/packages/backend/package.json index 4e2007bc00..6d4a99abbb 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -29,7 +29,6 @@ "dependencies": { "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md index cf846cf25e..98d757270e 100644 --- a/plugins/catalog-backend-module-aws/api-report.md +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -15,8 +15,8 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public @@ -96,8 +96,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AwsS3EntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index 0a9d9c97db..3a6178e614 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -69,7 +69,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -97,7 +97,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index d3a5a19a9b..7df7c3bede 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -59,7 +59,6 @@ "@aws-sdk/types": "^3.347.0", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts index 9165eb4322..b8a4256092 100644 --- a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleAwsS3EntityProvider } from './catalogModuleAwsS3EntityProvider'; @@ -23,7 +23,7 @@ import { AwsS3EntityProvider } from '../providers'; describe('catalogModuleAwsS3EntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts index 5367c17742..c5215438a7 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { AwsS3EntityProvider } from './AwsS3EntityProvider'; @@ -27,14 +27,14 @@ import 'aws-sdk-client-mock-jest'; import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'; import { mockServices } from '@backstage/backend-test-utils'; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -117,7 +117,7 @@ describe('AwsS3EntityProvider', () => { if (scheduleInConfig) { schedulingConfig.scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; } else { schedulingConfig.schedule = schedule; } @@ -392,7 +392,7 @@ describe('AwsS3EntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts index 0c58fe3f3a..f9246d01dd 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Integration, ScmIntegrations } from '@backstage/integration'; import { @@ -36,7 +35,11 @@ import { AwsCredentialsManager, DefaultAwsCredentialsManager, } from '@backstage/integration-aws-node'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; // TODO: event-based updates using S3 events (+ queue like SQS)? /** @@ -57,8 +60,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AwsS3EntityProvider[] { const providerConfigs = readAwsS3Configs(configRoot); @@ -107,7 +110,7 @@ export class AwsS3EntityProvider implements EntityProvider { private readonly integration: AwsS3Integration, private readonly awsCredentialsManager: AwsCredentialsManager, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), @@ -116,7 +119,9 @@ export class AwsS3EntityProvider implements EntityProvider { this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-aws/src/providers/config.ts b/plugins/catalog-backend-module-aws/src/providers/config.ts index 1ab52f9fd4..b9c49599f6 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Config } from './types'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; const DEFAULT_PROVIDER_ID = 'default'; @@ -49,7 +49,9 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config { const accountId = config.getOptionalString('accountId'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-aws/src/providers/types.ts b/plugins/catalog-backend-module-aws/src/providers/types.ts index 8e12b5f096..da470e4dfe 100644 --- a/plugins/catalog-backend-module-aws/src/providers/types.ts +++ b/plugins/catalog-backend-module-aws/src/providers/types.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; export type AwsS3Config = { id: string; bucketName: string; prefix?: string; region?: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; accountId?: string; }; diff --git a/plugins/catalog-backend-module-azure/api-report.md b/plugins/catalog-backend-module-azure/api-report.md index a449909dbf..9220b08ab4 100644 --- a/plugins/catalog-backend-module-azure/api-report.md +++ b/plugins/catalog-backend-module-azure/api-report.md @@ -10,9 +10,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { TaskRunner } from '@backstage/backend-tasks'; // @public export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { @@ -46,8 +46,8 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AzureDevOpsEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index 757ed2bfd3..f6c3bab068 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -52,7 +52,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 622be8707b..b951aa3584 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -52,7 +52,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts index e406c61e07..32eb190460 100644 --- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleAzureDevOpsEntityProvider } from './catalogModuleAzureDevOpsEntityProvider'; @@ -23,7 +23,7 @@ import { AzureDevOpsEntityProvider } from '../providers'; describe('catalogModuleAzureDevOpsEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts index b7ada509a7..0c462f2906 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { CodeSearchResultItem } from '../lib'; @@ -29,14 +29,14 @@ import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib'); const mockCodeSearch = codeSearch as jest.MockedFunction; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -83,7 +83,7 @@ describe('AzureDevOpsEntityProvider', () => { if (scheduleInConfig) { schedulingConfig.scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; } else { schedulingConfig.schedule = schedule; } @@ -275,7 +275,7 @@ describe('AzureDevOpsEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts index 60c7354636..cd3b4bbbad 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AzureDevOpsCredentialsProvider, @@ -32,7 +31,11 @@ import { readAzureDevOpsConfigs } from './config'; import { AzureDevOpsConfig } from './types'; import * as uuid from 'uuid'; import { codeSearch, CodeSearchResultItem } from '../lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + SchedulerService, + SchedulerServiceTaskRunner, + LoggerService, +} from '@backstage/backend-plugin-api'; /** * Provider which discovers catalog files within an Azure DevOps repositories. @@ -50,8 +53,8 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AzureDevOpsEntityProvider[] { const providerConfigs = readAzureDevOpsConfigs(configRoot); @@ -99,7 +102,7 @@ export class AzureDevOpsEntityProvider implements EntityProvider { private readonly integration: AzureIntegration, private readonly credentialsProvider: AzureDevOpsCredentialsProvider, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), @@ -108,7 +111,9 @@ export class AzureDevOpsEntityProvider implements EntityProvider { this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-azure/src/providers/config.ts b/plugins/catalog-backend-module-azure/src/providers/config.ts index 3c10a50863..17616b06bc 100644 --- a/plugins/catalog-backend-module-azure/src/providers/config.ts +++ b/plugins/catalog-backend-module-azure/src/providers/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { AzureDevOpsConfig } from './types'; @@ -45,7 +45,9 @@ function readAzureDevOpsConfig(id: string, config: Config): AzureDevOpsConfig { const path = config.getOptionalString('path') || '/catalog-info.yaml'; const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-azure/src/providers/types.ts b/plugins/catalog-backend-module-azure/src/providers/types.ts index 3cd827b380..5ca9dc94dc 100644 --- a/plugins/catalog-backend-module-azure/src/providers/types.ts +++ b/plugins/catalog-backend-module-azure/src/providers/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; export type AzureDevOpsConfig = { id: string; @@ -24,5 +24,5 @@ export type AzureDevOpsConfig = { repository: string; branch?: string; path: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 88c03b3dc6..036d4b5108 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -35,7 +35,6 @@ "dependencies": { "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index fe6425ffb9..959d07ad76 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -37,10 +37,11 @@ import { AuthService, DiscoveryService, LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; import * as uuid from 'uuid'; import lodash from 'lodash'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; const HTTP_VERBS: (keyof PathItemObject)[] = [ 'get', @@ -163,7 +164,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { public readonly discovery: DiscoveryService, public readonly logger: LoggerService, public readonly auth: AuthService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.scheduleFn = this.createScheduleFn(taskRunner); } @@ -173,7 +174,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { options: { discovery: DiscoveryService; logger: LoggerService; - schedule: PluginTaskScheduler; + schedule: SchedulerService; auth: AuthService; }, ) { @@ -204,7 +205,9 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { return await this.scheduleFn(); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md index 22845cf260..bba0b754e2 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md @@ -10,8 +10,8 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { Events } from '@backstage/plugin-bitbucket-cloud-common'; import { EventsService } from '@backstage/plugin-events-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { TokenManager } from '@backstage/backend-common'; // @public @@ -25,8 +25,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider { catalogApi?: CatalogApi; events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; tokenManager?: TokenManager; }, ): BitbucketCloudEntityProvider[]; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts index fa7c070042..1051976b24 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -58,7 +58,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the discovery. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -91,7 +91,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the discovery. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index de0652d01b..70d8a22d1c 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts index bcf13e786d..b3da110323 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { + createServiceFactory, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; import { startTestBackend, mockServices } from '@backstage/backend-test-utils'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; @@ -35,7 +37,7 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => { }, }); let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const catalogExtensionPointImpl = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts index 80d09f3e2f..4fbbb67c7d 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts @@ -16,10 +16,10 @@ import { TokenManager } from '@backstage/backend-common'; import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskInvocationDefinition, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -40,14 +40,14 @@ import { BitbucketCloudEntityProvider, } from './BitbucketCloudEntityProvider'; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -190,7 +190,7 @@ describe('BitbucketCloudEntityProvider', () => { }); it('fail with scheduler but no schedule config', () => { - const scheduler = jest.fn() as unknown as PluginTaskScheduler; + const scheduler = jest.fn() as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { @@ -214,7 +214,7 @@ describe('BitbucketCloudEntityProvider', () => { it('single simple provider config with schedule in config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts index 9135e0717f..6b19bea4f2 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts @@ -15,8 +15,11 @@ */ import { TokenManager } from '@backstage/backend-common'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { LocationEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; @@ -80,8 +83,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider { catalogApi?: CatalogApi; events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; tokenManager?: TokenManager; }, ): BitbucketCloudEntityProvider[] { @@ -124,7 +127,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { config: BitbucketCloudEntityProviderConfig, integration: BitbucketCloudIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, catalogApi?: CatalogApi, events?: EventsService, tokenManager?: TokenManager, @@ -140,7 +143,9 @@ export class BitbucketCloudEntityProvider implements EntityProvider { this.tokenManager = tokenManager; } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn( + schedule: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = this.getTaskId(); return schedule.run({ diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts index def88b0951..5a806d2e0b 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -31,7 +31,7 @@ export type BitbucketCloudEntityProviderConfig = { projectKey?: RegExp; repoSlug?: RegExp; }; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; export function readProviderConfigs( @@ -67,7 +67,9 @@ function readProviderConfig( const repoSlugPattern = config.getOptionalString('filters.repoSlug'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-bitbucket-server/api-report.md b/plugins/catalog-backend-module-bitbucket-server/api-report.md index e5987e44cf..211096639c 100644 --- a/plugins/catalog-backend-module-bitbucket-server/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-server/api-report.md @@ -10,9 +10,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; // @public export class BitbucketServerClient { @@ -57,8 +57,8 @@ export class BitbucketServerEntityProvider implements EntityProvider { options: { logger: LoggerService; parser?: BitbucketServerLocationParser; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): BitbucketServerEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index b02e0e9a24..0f6889efa7 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -54,7 +54,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -86,7 +86,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 55bc4a3f76..d17c970987 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -51,7 +51,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts index 5335b04e2d..fbd4fa7a36 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleBitbucketServerEntityProvider } from './catalogModuleBitbucketServerEntityProvider'; @@ -23,7 +23,7 @@ import { BitbucketServerEntityProvider } from '../providers'; describe('catalogModuleBitbucketServerEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index 49b69f0fa6..05e0ea6a01 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -30,14 +30,14 @@ import { setupServer } from 'msw/node'; import { BitbucketServerEntityProvider } from './BitbucketServerEntityProvider'; import { BitbucketServerPagedResponse } from '../lib'; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -412,7 +412,7 @@ describe('BitbucketServerEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { @@ -469,7 +469,7 @@ describe('BitbucketServerEntityProvider', () => { const schedule = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), refresh: jest.fn(), diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts index 88f86eb87c..8e26d6a866 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -36,7 +35,11 @@ import { BitbucketServerLocationParser, defaultBitbucketServerLocationParser, } from './BitbucketServerLocationParser'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** * Discovers catalog files located in Bitbucket Server. @@ -59,8 +62,8 @@ export class BitbucketServerEntityProvider implements EntityProvider { options: { logger: LoggerService; parser?: BitbucketServerLocationParser; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): BitbucketServerEntityProvider[] { const integrations = ScmIntegrations.fromConfig(config); @@ -103,7 +106,7 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: BitbucketServerEntityProviderConfig, integration: BitbucketServerIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, parser?: BitbucketServerLocationParser, ) { this.integration = integration; @@ -115,7 +118,9 @@ export class BitbucketServerEntityProvider implements EntityProvider { this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts index 2f1bb7227d..018ff23069 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -32,7 +32,7 @@ export type BitbucketServerEntityProviderConfig = { repoSlug?: RegExp; skipArchivedRepos?: boolean; }; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; export function readProviderConfigs( @@ -70,7 +70,9 @@ function readProviderConfig( ); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-gcp/config.d.ts b/plugins/catalog-backend-module-gcp/config.d.ts index d6884d32ce..dd657420f7 100644 --- a/plugins/catalog-backend-module-gcp/config.d.ts +++ b/plugins/catalog-backend-module-gcp/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -37,7 +37,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule: TaskScheduleDefinitionConfig; + schedule: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 39f875ecea..a45c7beb34 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -52,7 +52,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts index 637963d8e3..b9eb4cf2aa 100644 --- a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts @@ -15,7 +15,7 @@ */ import { GkeEntityProvider } from './GkeEntityProvider'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import * as container from '@google-cloud/container'; import { ConfigReader } from '@backstage/config'; @@ -30,7 +30,7 @@ describe('GkeEntityProvider', () => { const taskRunner = { createScheduleFn: jest.fn(), run: jest.fn(), - } as TaskRunner; + } as SchedulerServiceTaskRunner; const schedulerMock = { createScheduledTaskRunner: jest.fn(), } as any; diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts index d644a8383c..13bf245f4e 100644 --- a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts +++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts @@ -13,10 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - readTaskScheduleDefinitionFromConfig, - TaskRunner, -} from '@backstage/backend-tasks'; import { DeferredEntity, EntityProvider, @@ -31,7 +27,12 @@ import { ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS, } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; -import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -51,7 +52,7 @@ export class GkeEntityProvider implements EntityProvider { private constructor( logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, gkeParents: string[], clusterManagerClient: container.v1.ClusterManagerClient, ) { @@ -90,7 +91,7 @@ export class GkeEntityProvider implements EntityProvider { clusterManagerClient: container.v1.ClusterManagerClient; }) { const gkeProviderConfig = config.getConfig('catalog.providers.gcp.gke'); - const schedule = readTaskScheduleDefinitionFromConfig( + const schedule = readSchedulerServiceTaskScheduleDefinitionFromConfig( gkeProviderConfig.getConfig('schedule'), ); return new GkeEntityProvider( @@ -172,7 +173,9 @@ export class GkeEntityProvider implements EntityProvider { }; } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gerrit/api-report.md b/plugins/catalog-backend-module-gerrit/api-report.md index a4620a1c15..abf2a83e7b 100644 --- a/plugins/catalog-backend-module-gerrit/api-report.md +++ b/plugins/catalog-backend-module-gerrit/api-report.md @@ -7,8 +7,8 @@ import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; // @public (undocumented) export class GerritEntityProvider implements EntityProvider { @@ -19,8 +19,8 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GerritEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 85e927de83..ebbaae84ab 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -48,7 +48,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", diff --git a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts index dc40e0525d..503b2a41d0 100644 --- a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleGerritEntityProvider } from './catalogModuleGerritEntityProvider'; @@ -23,7 +23,7 @@ import { GerritEntityProvider } from '../providers/GerritEntityProvider'; describe('catalogModuleGerritEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts index ee3555c7f9..5f07f96452 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -41,14 +41,14 @@ const getJsonFixture = (fileName: string) => ), ); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -228,7 +228,7 @@ describe('GerritEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; expect(() => GerritEntityProvider.fromConfig(config, { logger, @@ -270,7 +270,7 @@ describe('GerritEntityProvider', () => { }); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const repoBuffer = fs.readFileSync( path.resolve(__dirname, '__fixtures__/listProjectsBody.txt'), diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts index ee4a33f395..bd03b90c52 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { @@ -35,7 +34,11 @@ import * as uuid from 'uuid'; import { readGerritConfigs } from './config'; import { GerritProjectQueryResult, GerritProviderConfig } from './types'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** @public */ export class GerritEntityProvider implements EntityProvider { @@ -49,8 +52,8 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GerritEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -95,7 +98,7 @@ export class GerritEntityProvider implements EntityProvider { config: GerritProviderConfig, integration: GerritIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.config = config; this.integration = integration; @@ -114,7 +117,9 @@ export class GerritEntityProvider implements EntityProvider { await this.scheduleFn(); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gerrit/src/providers/config.ts b/plugins/catalog-backend-module-gerrit/src/providers/config.ts index dd7ee9ec43..36c8283545 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/config.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { GerritProviderConfig } from './types'; @@ -24,7 +24,9 @@ function readGerritConfig(id: string, config: Config): GerritProviderConfig { const query = config.getString('query'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-gerrit/src/providers/types.ts b/plugins/catalog-backend-module-gerrit/src/providers/types.ts index 4a8329df86..338ae41427 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/types.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; export type GerritProjectInfo = { id: string; @@ -30,5 +30,5 @@ export type GerritProviderConfig = { query: string; id: string; branch?: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 969f985e5e..d39105b10a 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -34,7 +34,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-backend-module-github": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", diff --git a/plugins/catalog-backend-module-github-org/src/module.test.ts b/plugins/catalog-backend-module-github-org/src/module.test.ts index 76ab4d1fbc..ba69f3c842 100644 --- a/plugins/catalog-backend-module-github-org/src/module.test.ts +++ b/plugins/catalog-backend-module-github-org/src/module.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; @@ -23,7 +23,7 @@ import { catalogModuleGithubOrgEntityProvider } from './module'; describe('catalogModuleGithubOrgEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (...providers: any) => { diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 31d4cef1c0..adb6f6c42d 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -18,11 +18,9 @@ import { coreServices, createBackendModule, createExtensionPoint, + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, } from '@backstage/backend-plugin-api'; -import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubMultiOrgEntityProvider, @@ -134,7 +132,7 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ id: string; githubUrl: string; orgs?: string[]; - schedule: TaskScheduleDefinition; + schedule: SchedulerServiceTaskScheduleDefinition; }> { const baseKey = 'catalog.providers.githubOrg'; const baseConfig = rootConfig.getOptional(baseKey); @@ -150,6 +148,8 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ id: c.getString('id'), githubUrl: c.getString('githubUrl'), orgs: c.getOptionalStringArray('orgs'), - schedule: readTaskScheduleDefinitionFromConfig(c.getConfig('schedule')), + schedule: readSchedulerServiceTaskScheduleDefinitionFromConfig( + c.getConfig('schedule'), + ), })); } diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 0b68170255..dad81f8681 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -20,10 +20,10 @@ import { graphql } from '@octokit/graphql'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node'; -import { TaskRunner } from '@backstage/backend-tasks'; import { TokenManager } from '@backstage/backend-common'; import { UserEntity } from '@backstage/catalog-model'; @@ -70,8 +70,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitHubEntityProvider[]; // (undocumented) @@ -90,8 +90,8 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { options: { events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GithubEntityProvider[]; // (undocumented) @@ -173,7 +173,7 @@ export interface GithubMultiOrgEntityProviderOptions { id: string; logger: LoggerService; orgs?: string[]; - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; teamTransformer?: TeamTransformer; userTransformer?: UserTransformer; } @@ -251,7 +251,7 @@ export interface GithubOrgEntityProviderOptions { id: string; logger: LoggerService; orgUrl: string; - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; teamTransformer?: TeamTransformer; userTransformer?: UserTransformer; } diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index 8b0f266c14..cc197f3ec7 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -116,7 +116,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -185,7 +185,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; @@ -220,7 +220,7 @@ export interface Config { /** * The refresh schedule to use. */ - schedule: TaskScheduleDefinitionConfig; + schedule: SchedulerServiceTaskScheduleDefinitionConfig; } | Array<{ /** @@ -249,7 +249,7 @@ export interface Config { /** * The refresh schedule to use. */ - schedule: TaskScheduleDefinitionConfig; + schedule: SchedulerServiceTaskScheduleDefinitionConfig; }>; }; }; diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 9c97ad2330..d7cdc0df07 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-github/src/deprecated.ts b/plugins/catalog-backend-module-github/src/deprecated.ts index cd1d7db62a..2a82714b4c 100644 --- a/plugins/catalog-backend-module-github/src/deprecated.ts +++ b/plugins/catalog-backend-module-github/src/deprecated.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { EntityProvider, @@ -25,7 +24,11 @@ import { GithubOrgEntityProvider, GithubOrgEntityProviderOptions, } from './providers/GithubOrgEntityProvider'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** * @public @@ -58,8 +61,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitHubEntityProvider[] { options.logger.warn( diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts index 018d339de8..3509823819 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { @@ -27,7 +27,7 @@ import { GithubLocationAnalyzer } from '../analyzers/GithubLocationAnalyzer'; describe('githubCatalogModule', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 79af5ad5ca..624deaa38e 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { DeferredEntity, @@ -41,14 +41,14 @@ jest.mock('../lib/github', () => { getOrganizationRepositories: jest.fn(), }; }); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -624,7 +624,7 @@ describe('GithubEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = createSingleProviderConfig(); expect(() => @@ -641,7 +641,7 @@ describe('GithubEntityProvider', () => { const schedule = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = createSingleProviderConfig({ providerConfig: { schedule: { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 38008fe03c..1c64174ee7 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubCredentialsProvider, @@ -66,7 +65,11 @@ import { RepositoryUnarchivedEvent, } from '@octokit/webhooks-types'; import { Minimatch } from 'minimatch'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; const EVENT_TOPICS = ['github.push', 'github.repository']; @@ -103,8 +106,8 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { options: { events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GithubEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -147,7 +150,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { config: GithubEntityProviderConfig, integration: GithubIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, events?: EventsService, ) { this.config = config; @@ -177,7 +180,9 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { return await this.scheduleFn(); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts index 296a508adf..073c92701f 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -36,7 +36,7 @@ export type GithubEntityProviderConfig = { visibility?: string[]; }; validateLocationsExist: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; export type GithubTopicFilters = { @@ -96,7 +96,9 @@ function readProviderConfig( } const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 756b9902c6..4367383cd2 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -78,7 +77,10 @@ import { } from '../lib/github'; import { splitTeamSlug } from '../lib/util'; import { areGroupEntities, areUserEntities } from '../lib/guards'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; const EVENT_TOPICS = [ 'github.installation', @@ -128,10 +130,10 @@ export interface GithubMultiOrgEntityProviderOptions { * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; /** * The logger to use. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index ba7602f962..f7183b4df9 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { Entity, isGroupEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -103,10 +103,10 @@ export interface GithubOrgEntityProviderOptions { * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; /** * The logger to use. diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 54a9a5f7bf..b621ad5505 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -40,7 +40,6 @@ "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { - "@backstage/backend-tasks": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-events-backend-test-utils": "workspace:^", diff --git a/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts index 5fbc321cc2..1710467d24 100644 --- a/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { + createServiceFactory, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -35,7 +37,7 @@ describe('catalogModuleGitlabOrgDiscoveryEntityProvider', () => { }, }); let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index 0a1e4b7d36..8a21e237b5 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -13,9 +13,9 @@ import { GitLabIntegrationConfig } from '@backstage/integration'; import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; // @public @@ -28,8 +28,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitlabDiscoveryEntityProvider[]; // (undocumented) @@ -83,8 +83,8 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer; groupEntitiesTransformer?: GroupTransformer; groupNameTransformer?: GroupNameTransformer; @@ -108,7 +108,7 @@ export type GitlabProviderConfig = { groupPattern: RegExp; allowInherited?: boolean; orgEnabled?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; skipForkedRepos?: boolean; excludeRepos?: string[]; }; diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 9f009ee478..e249f13d4c 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -46,7 +46,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; /** * (Optional) RegExp for the Project Name Pattern */ diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 9853904759..623b690057 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 69c8ed5f92..61f76d03de 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { GitLabIntegrationConfig } from '@backstage/integration'; @@ -196,7 +196,7 @@ export type GitlabProviderConfig = { allowInherited?: boolean; orgEnabled?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; /** * If the project is a fork, skip repository */ diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts index 636405220b..944019cc69 100644 --- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { + createServiceFactory, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; @@ -35,7 +37,7 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => { }, }); let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index f5e167e564..e6309a8516 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -35,14 +35,14 @@ const server = setupServer(...handlers); registerMswTestHooks(server); afterEach(() => jest.clearAllMocks()); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -75,7 +75,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { it('should fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader(mock.config_no_schedule_integration); expect(() => diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index bbd90795be..fdd884e1fd 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-common'; @@ -68,8 +71,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitlabDiscoveryEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -121,7 +124,7 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { integration: GitLabIntegration; logger: LoggerService; events?: EventsService; - taskRunner: TaskRunner; + taskRunner: SchedulerServiceTaskRunner; }) { this.config = options.config; this.integration = options.integration; @@ -165,7 +168,9 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { * @param taskRunner - The task runner instance. * @returns The scheduled function. */ - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index 9706384ff0..a080948d65 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -36,14 +36,14 @@ const server = setupServer(...handlers); registerMswTestHooks(server); afterEach(() => jest.clearAllMocks()); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -113,7 +113,7 @@ describe('GitlabOrgDiscoveryEntityProvider - configuration', () => { it('should fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader(mock.config_org_integration_saas); expect(() => @@ -130,7 +130,7 @@ describe('GitlabOrgDiscoveryEntityProvider - configuration', () => { const schedule = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader(mock.config_org_integration_saas_sched); const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, { logger, diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index d525e3290c..7f06079f42 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -13,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -114,8 +117,8 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer; groupEntitiesTransformer?: GroupEntitiesTransformer; groupNameTransformer?: GroupNameTransformer; @@ -176,7 +179,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { integration: GitLabIntegration; logger: LoggerService; events?: EventsService; - taskRunner: TaskRunner; + taskRunner: SchedulerServiceTaskRunner; userTransformer?: UserTransformer; groupEntitiesTransformer?: GroupEntitiesTransformer; groupNameTransformer?: GroupNameTransformer; @@ -321,7 +324,9 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { } } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index d26cba5328..c0fac9a166 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { GitlabProviderConfig } from '../lib'; @@ -51,7 +51,9 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { config.getOptionalStringArray('excludeRepos') ?? []; const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; const restrictUsersToGroup = config.getOptionalBoolean('restrictUsersToGroup') ?? false; diff --git a/plugins/catalog-backend-module-incremental-ingestion/api-report.md b/plugins/catalog-backend-module-incremental-ingestion/api-report.md index 92666f3928..22b497b2f7 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/api-report.md +++ b/plugins/catalog-backend-module-incremental-ingestion/api-report.md @@ -14,8 +14,8 @@ import { EventSubscriber } from '@backstage/plugin-events-node'; import type { Logger } from 'winston'; import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import type { PluginDatabaseManager } from '@backstage/backend-common'; -import type { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Router } from 'express'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public @@ -89,7 +89,7 @@ export interface IncrementalEntityProviderOptions { export type PluginEnvironment = { logger: Logger; database: PluginDatabaseManager; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; config: Config; reader: UrlReaderService; permissions: PermissionEvaluator; diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 60af5a5c46..33cfbb1745 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts index eccfaee655..0f98384893 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { IncrementalEntityProvider } from '../types'; @@ -66,8 +66,7 @@ describe('WrapperProviders', () => { config, logger, client, - scheduler: - scheduler as Partial as PluginTaskScheduler, + scheduler: scheduler as Partial as SchedulerService, applyDatabaseMigrations, }); const wrapped1 = providers.wrap(provider1, { diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts index 7c637de841..79368638f2 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts @@ -15,10 +15,6 @@ */ import type { PluginDatabaseManager } from '@backstage/backend-common'; -import type { - PluginTaskScheduler, - TaskFunction, -} from '@backstage/backend-tasks'; import type { Config } from '@backstage/config'; import type { DeferredEntity, @@ -29,7 +25,12 @@ import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import type { DurationObjectUnits } from 'luxon'; import type { Logger } from 'winston'; import { IncrementalIngestionDatabaseManager } from './database/IncrementalIngestionDatabaseManager'; -import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + UrlReaderService, + SchedulerService, + SchedulerServiceTaskFunction, +} from '@backstage/backend-plugin-api'; /** * Ingest entities into the catalog in bite-sized chunks. @@ -187,14 +188,14 @@ export interface IncrementalEntityProviderOptions { export type PluginEnvironment = { logger: Logger; database: PluginDatabaseManager; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; config: Config; reader: UrlReaderService; permissions: PermissionEvaluator; }; export interface IterationEngine { - taskFn: TaskFunction; + taskFn: SchedulerServiceTaskFunction; } export interface IterationEngineOptions { diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index d1a1b0079a..b1811d23f4 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -39,7 +39,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 57f50fbd18..acc3b8531b 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -12,10 +12,10 @@ import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { TokenCredential } from '@azure/identity'; import { UserEntity } from '@backstage/catalog-model'; @@ -147,7 +147,7 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { logger: LoggerService; organizationTransformer?: OrganizationTransformer; providerConfigTransformer?: ProviderConfigTransformer; - schedule: 'manual' | TaskRunner; + schedule: 'manual' | SchedulerServiceTaskRunner; target: string; userTransformer?: UserTransformer; } @@ -157,8 +157,8 @@ export type MicrosoftGraphOrgEntityProviderOptions = | MicrosoftGraphOrgEntityProviderLegacyOptions | { logger: LoggerService; - schedule?: 'manual' | TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: 'manual' | SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer | Record; groupTransformer?: GroupTransformer | Record; organizationTransformer?: @@ -218,7 +218,7 @@ export type MicrosoftGraphProviderConfig = { groupIncludeSubGroups?: boolean; queryMode?: 'basic' | 'advanced'; loadUserPhotos?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; // @public diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index a470c075f0..b93026e1bb 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -217,7 +217,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -304,7 +304,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 5275613e16..4823948c2d 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -53,7 +53,6 @@ "dependencies": { "@azure/identity": "^4.0.0", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 257a7d5736..e47181a1c4 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { trimEnd } from 'lodash'; @@ -141,7 +141,7 @@ export type MicrosoftGraphProviderConfig = { /** * Schedule configuration for refresh tasks. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; /** @@ -338,7 +338,9 @@ export function readProviderConfig( } const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts index 2a3e66a9b3..9f4c616c09 100644 --- a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleMicrosoftGraphOrgEntityProvider } from './catalogModuleMicrosoftGraphOrgEntityProvider'; @@ -23,7 +23,7 @@ import { MicrosoftGraphOrgEntityProvider } from '../processors'; describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index 78efa1b3c1..1d68f706bc 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { ANNOTATION_LOCATION, @@ -48,14 +48,14 @@ const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock< Promise<{ users: UserEntity[]; groups: GroupEntity[] }> >; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -102,7 +102,7 @@ describe('MicrosoftGraphOrgEntityProvider', () => { const taskRunner = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => taskRunner, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), refresh: jest.fn(), diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index a019ac79a3..4c92a5f17e 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -41,7 +40,11 @@ import { UserTransformer, } from '../microsoftGraph'; import { readProviderConfigs } from '../microsoftGraph/config'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** * Options for {@link MicrosoftGraphOrgEntityProvider}. @@ -65,16 +68,16 @@ export type MicrosoftGraphOrgEntityProviderOptions = * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; /** * Scheduler used to schedule refreshes based on * the schedule config. */ - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; /** * The function that transforms a user entry in msgraph to an entity. @@ -141,10 +144,10 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule: 'manual' | TaskRunner; + schedule: 'manual' | SchedulerServiceTaskRunner; /** * The function that transforms a user entry in msgraph to an entity. @@ -360,7 +363,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule(taskRunner: TaskRunner) { + private schedule(taskRunner: SchedulerServiceTaskRunner) { this.scheduleFn = async () => { const id = `${this.getProviderName()}:refresh`; await taskRunner.run({ diff --git a/plugins/catalog-backend-module-puppetdb/api-report.md b/plugins/catalog-backend-module-puppetdb/api-report.md index 3361313392..691b752a6b 100644 --- a/plugins/catalog-backend-module-puppetdb/api-report.md +++ b/plugins/catalog-backend-module-puppetdb/api-report.md @@ -8,10 +8,10 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { JsonValue } from '@backstage/types'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ResourceEntity } from '@backstage/catalog-model'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; // @public export const ANNOTATION_PUPPET_CERTNAME = 'puppet.com/certname'; @@ -30,8 +30,8 @@ export class PuppetDbEntityProvider implements EntityProvider { config: Config, deps: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; transformer?: ResourceTransformer; }, ): PuppetDbEntityProvider[]; @@ -45,7 +45,7 @@ export type PuppetDbEntityProviderConfig = { id: string; baseUrl: string; query?: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; // @public diff --git a/plugins/catalog-backend-module-puppetdb/config.d.ts b/plugins/catalog-backend-module-puppetdb/config.d.ts index 8aab239047..39091efa93 100644 --- a/plugins/catalog-backend-module-puppetdb/config.d.ts +++ b/plugins/catalog-backend-module-puppetdb/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; /** * Represents the configuration for the Backstage. @@ -44,7 +44,7 @@ export interface Config { /** * (Optional) Task schedule definition for the refresh. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; } | { [name: string]: { @@ -59,7 +59,7 @@ export interface Config { /** * (Optional) Task schedule definition for the refresh. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; }; }; diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 94b6bcf591..4c77ab8168 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -54,7 +54,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts index 4fee7e2cff..68f8b08a3b 100644 --- a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts +++ b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModulePuppetDbEntityProvider } from './catalogModulePuppetDbEntityProvider'; @@ -23,7 +23,7 @@ import { PuppetDbEntityProvider } from '../providers/PuppetDbEntityProvider'; describe('catalogModulePuppetDbEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts index bd76aa2048..bdeed6d2df 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { PuppetDbEntityProvider } from './PuppetDbEntityProvider'; import { @@ -39,14 +42,14 @@ jest.mock('../puppet/read', () => { const logger = mockServices.logger.mock(); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts index 0496cc033b..fc13f45c39 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts @@ -23,7 +23,6 @@ import { readProviderConfigs, } from './PuppetDbEntityProviderConfig'; import { Config } from '@backstage/config'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import * as uuid from 'uuid'; import { defaultResourceTransformer, ResourceTransformer } from '../puppet'; import { @@ -34,7 +33,11 @@ import { import { merge } from 'lodash'; import { readPuppetNodes } from '../puppet/read'; import { ENDPOINT_NODES } from '../puppet/constants'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + SchedulerService, + SchedulerServiceTaskRunner, + LoggerService, +} from '@backstage/backend-plugin-api'; /** * Reads nodes from [PuppetDB](https://www.puppet.com/docs/puppet/6/puppetdb_overview.html) @@ -61,8 +64,8 @@ export class PuppetDbEntityProvider implements EntityProvider { config: Config, deps: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; transformer?: ResourceTransformer; }, ): PuppetDbEntityProvider[] { @@ -97,7 +100,7 @@ export class PuppetDbEntityProvider implements EntityProvider { * * @param config - Configuration of the provider. * @param logger - The instance of a {@link LoggerService}. - * @param taskRunner - The instance of {@link TaskRunner}. + * @param taskRunner - The instance of {@link SchedulerServiceTaskRunner}. * @param transformer - A {@link ResourceTransformer} function. * * @private @@ -105,7 +108,7 @@ export class PuppetDbEntityProvider implements EntityProvider { private constructor( config: PuppetDbEntityProviderConfig, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, transformer: ResourceTransformer, ) { this.config = config; @@ -130,11 +133,13 @@ export class PuppetDbEntityProvider implements EntityProvider { /** * Creates a function that can be used to schedule a refresh of the catalog. * - * @param taskRunner - The instance of {@link TaskRunner}. + * @param taskRunner - The instance of {@link SchedulerServiceTaskRunner}. * * @private */ - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts index 7b95b7f387..ed640b190e 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { DEFAULT_PROVIDER_ID } from './constants'; @@ -42,7 +42,7 @@ export type PuppetDbEntityProviderConfig = { /** * (Optional) Task schedule definition for the refresh. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; /** @@ -87,7 +87,9 @@ function readProviderConfig( const query = config.getOptionalString('query'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 1433cd51ca..7717066d2a 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -58,9 +58,9 @@ import { PlaceholderResolverParams as PlaceholderResolverParams_2 } from '@backs import { PlaceholderResolverRead as PlaceholderResolverRead_2 } from '@backstage/plugin-catalog-node'; import { PlaceholderResolverResolveUrl as PlaceholderResolverResolveUrl_2 } from '@backstage/plugin-catalog-node'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node'; import { TokenManager } from '@backstage/backend-common'; @@ -200,7 +200,7 @@ export type CatalogEnvironment = { config: RootConfigService; reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; discovery?: DiscoveryService; auth?: AuthService; httpAuth?: HttpAuthService; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index c37bef01a5..d374f63ada 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -64,7 +64,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 6020811ab0..e42847bafe 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -29,7 +29,6 @@ import { createCounterMetric, createSummaryMetric } from '../util/metrics'; import { CatalogProcessingOrchestrator, EntityProcessingResult } from './types'; import { Stitcher, stitchingStrategyFromConfig } from '../stitching/types'; import { startTaskPipeline } from './TaskPipeline'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { addEntityAttributes, @@ -39,7 +38,7 @@ import { import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities'; import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { CATALOG_ERRORS_TOPIC } from '../constants'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; const CACHE_TTL = 5; @@ -55,7 +54,7 @@ export type ProgressTracker = ReturnType; // is just one. export class DefaultCatalogProcessingEngine { private readonly config: Config; - private readonly scheduler?: PluginTaskScheduler; + private readonly scheduler?: SchedulerService; private readonly logger: LoggerService; private readonly knex: Knex; private readonly processingDatabase: ProcessingDatabase; @@ -75,7 +74,7 @@ export class DefaultCatalogProcessingEngine { constructor(options: { config: Config; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; logger: LoggerService; knex: Knex; processingDatabase: ProcessingDatabase; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 585ed272e7..8f020c8c7c 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -18,7 +18,6 @@ import { createLegacyAuthAdapters, HostDiscovery, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { DefaultNamespaceEntityPolicy, Entity, @@ -113,6 +112,7 @@ import { PermissionsService, RootConfigService, UrlReaderService, + SchedulerService, } from '@backstage/backend-plugin-api'; /** @@ -134,7 +134,7 @@ export type CatalogEnvironment = { config: RootConfigService; reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; discovery?: DiscoveryService; auth?: AuthService; httpAuth?: HttpAuthService; diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 776cabce17..8207db879d 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -47,12 +47,12 @@ import { validateRequestBody, } from './util'; import { createOpenApiRouter } from '../schema/openapi.generated'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { parseEntityPaginationParams } from './request/parseEntityPaginationParams'; import { AuthService, HttpAuthService, LoggerService, + SchedulerService, } from '@backstage/backend-plugin-api'; import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; @@ -68,7 +68,7 @@ export interface RouterOptions { locationService: LocationService; orchestrator?: CatalogProcessingOrchestrator; refreshService?: RefreshService; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; logger: LoggerService; config: Config; permissionIntegrationRouter?: express.Router; diff --git a/plugins/events-backend-module-aws-sqs/api-report.md b/plugins/events-backend-module-aws-sqs/api-report.md index ff863e4d8d..ac806bf01b 100644 --- a/plugins/events-backend-module-aws-sqs/api-report.md +++ b/plugins/events-backend-module-aws-sqs/api-report.md @@ -6,7 +6,7 @@ import { Config } from '@backstage/config'; import { EventsService } from '@backstage/plugin-events-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; // @public export class AwsSqsConsumingEventPublisher { @@ -15,7 +15,7 @@ export class AwsSqsConsumingEventPublisher { config: Config; events: EventsService; logger: LoggerService; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; }): AwsSqsConsumingEventPublisher[]; // (undocumented) start(): Promise; diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 32294e7543..e2dd0ea557 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -50,7 +50,6 @@ "@aws-sdk/client-sqs": "^3.350.0", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts index db4f4ffa32..6b1f9ec54c 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts @@ -19,7 +19,7 @@ import { ReceiveMessageCommand, SQSClient, } from '@aws-sdk/client-sqs'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; import { mockClient } from 'aws-sdk-client-mock'; @@ -56,7 +56,7 @@ describe('AwsSqsConsumingEventPublisher', () => { const events = new TestEventsService(); const scheduler = { scheduleTask: jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const publishers = AwsSqsConsumingEventPublisher.fromConfig({ config, @@ -90,7 +90,7 @@ describe('AwsSqsConsumingEventPublisher', () => { const events = new TestEventsService(); const scheduler = { scheduleTask: jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const publishers = AwsSqsConsumingEventPublisher.fromConfig({ config, @@ -141,7 +141,7 @@ describe('AwsSqsConsumingEventPublisher', () => { scheduleTask: (spec: { fn: () => Promise }) => { taskFn = spec.fn; }, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; // on the first attempt, we will return 1 message and 0 messages afterwards const sqsMock = mockClient(SQSClient); diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts index de55dee017..c7628ffb20 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -22,7 +22,7 @@ import { SQSClient, } from '@aws-sdk/client-sqs'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { EventsService } from '@backstage/plugin-events-node'; import { AwsSqsEventSourceConfig, readConfig } from './config'; @@ -46,7 +46,7 @@ export class AwsSqsConsumingEventPublisher { config: Config; events: EventsService; logger: LoggerService; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; }): AwsSqsConsumingEventPublisher[] { return readConfig(env.config).map( config => @@ -62,7 +62,7 @@ export class AwsSqsConsumingEventPublisher { private constructor( private readonly logger: LoggerService, private readonly events: EventsService, - private readonly scheduler: PluginTaskScheduler, + private readonly scheduler: SchedulerService, config: AwsSqsEventSourceConfig, ) { this.topic = config.topic; diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts index eeb650ff6c..7bad1e38d1 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts @@ -34,7 +34,7 @@ export interface AwsSqsEventSourceConfig { endpoint?: string; } -// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-tasks +// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-plugin-api function readOptionalHumanDuration( config: Config, key: string, diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 207b54e63f..6f6063a252 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -37,10 +37,10 @@ import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { Schema } from 'jsonschema'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; @@ -509,7 +509,7 @@ export interface RouterOptions { // (undocumented) reader: UrlReaderService; // (undocumented) - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; // (undocumented) taskBroker?: TaskBroker_2; // @deprecated (undocumented) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 459111c835..61862ce636 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -64,7 +64,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 7dcf702bf7..4f310e1846 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -19,7 +19,6 @@ import { HostDiscovery, PluginDatabaseManager, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef, @@ -88,6 +87,7 @@ import { LifecycleService, PermissionsService, UrlReaderService, + SchedulerService, } from '@backstage/backend-plugin-api'; import { IdentityApi, @@ -148,7 +148,7 @@ export interface RouterOptions { lifecycle?: LifecycleService; database: PluginDatabaseManager; catalogClient: CatalogApi; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; actions?: TemplateAction[]; /** * @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker diff --git a/plugins/search-backend-module-catalog/config.d.ts b/plugins/search-backend-module-catalog/config.d.ts index 0105764afb..be9122b907 100644 --- a/plugins/search-backend-module-catalog/config.d.ts +++ b/plugins/search-backend-module-catalog/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { search?: { @@ -48,7 +48,7 @@ export interface Config { /** * The schedule for how often to run the collation job. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index d2597aa27d..2b17facb38 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -50,7 +50,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/search-backend-module-catalog/src/collators/config.ts b/plugins/search-backend-module-catalog/src/collators/config.ts index d4ae9b5934..afc94052df 100644 --- a/plugins/search-backend-module-catalog/src/collators/config.ts +++ b/plugins/search-backend-module-catalog/src/collators/config.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { EntityFilterQuery } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -39,15 +39,16 @@ export const defaults = { export function readScheduleConfigOptions( configRoot: Config, -): TaskScheduleDefinition { - let schedule: TaskScheduleDefinition | undefined = undefined; +): SchedulerServiceTaskScheduleDefinition { + let schedule: SchedulerServiceTaskScheduleDefinition | undefined = undefined; const config = configRoot.getOptionalConfig(configKey); if (config) { const scheduleConfig = config.getOptionalConfig('schedule'); if (scheduleConfig) { try { - schedule = readTaskScheduleDefinitionFromConfig(scheduleConfig); + schedule = + readSchedulerServiceTaskScheduleDefinitionFromConfig(scheduleConfig); } catch (error) { throw new InputError(`Invalid schedule at ${configKey}, ${error}`); } diff --git a/plugins/search-backend-module-explore/config.d.ts b/plugins/search-backend-module-explore/config.d.ts index 73ff015f20..21b6f16bf8 100644 --- a/plugins/search-backend-module-explore/config.d.ts +++ b/plugins/search-backend-module-explore/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { search?: { @@ -26,7 +26,7 @@ export interface Config { /** * The schedule for how often to run the collation job. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 2c6e767668..f7adba41a8 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -51,7 +51,6 @@ "@backstage-community/plugin-explore-common": "^0.0.4", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-search-common": "workspace:^", diff --git a/plugins/search-backend-module-explore/src/alpha.ts b/plugins/search-backend-module-explore/src/alpha.ts index 21c5901885..ee4bedde21 100644 --- a/plugins/search-backend-module-explore/src/alpha.ts +++ b/plugins/search-backend-module-explore/src/alpha.ts @@ -22,11 +22,11 @@ import { coreServices, createBackendModule, + readSchedulerServiceTaskScheduleDefinitionFromConfig, } from '@backstage/backend-plugin-api'; import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha'; import { ToolDocumentCollatorFactory } from '@backstage/plugin-search-backend-module-explore'; -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; /** * Search backend module for the Explore index. @@ -63,7 +63,7 @@ export default createBackendModule({ }; const schedule = config.has('search.collators.explore.schedule') - ? readTaskScheduleDefinitionFromConfig( + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( config.getConfig('search.collators.explore.schedule'), ) : defaultSchedule; diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index a7006fbfac..3c104a4c96 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -37,7 +37,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-search-common": "workspace:^", diff --git a/plugins/search-backend-module-stack-overflow-collator/src/module/SearchStackOverflowCollatorModule.ts b/plugins/search-backend-module-stack-overflow-collator/src/module/SearchStackOverflowCollatorModule.ts index bb35b3a766..4ee7d93a82 100644 --- a/plugins/search-backend-module-stack-overflow-collator/src/module/SearchStackOverflowCollatorModule.ts +++ b/plugins/search-backend-module-stack-overflow-collator/src/module/SearchStackOverflowCollatorModule.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { coreServices, createBackendModule, @@ -46,7 +46,7 @@ export const searchStackOverflowCollatorModule = createBackendModule({ }; const schedule = config.has('stackoverflow.schedule') - ? readTaskScheduleDefinitionFromConfig( + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( config.getConfig('stackoverflow.schedule'), ) : defaultSchedule; diff --git a/plugins/search-backend-module-techdocs/config.d.ts b/plugins/search-backend-module-techdocs/config.d.ts index 630bfc3692..ff0606215c 100644 --- a/plugins/search-backend-module-techdocs/config.d.ts +++ b/plugins/search-backend-module-techdocs/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { search?: { @@ -26,7 +26,7 @@ export interface Config { /** * The schedule for how often to run the collation job. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; /** * A templating string with placeholders, to form the final location of * the entity. diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 8e95262f87..540c64dcc2 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -50,7 +50,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/search-backend-module-techdocs/src/alpha.ts b/plugins/search-backend-module-techdocs/src/alpha.ts index fb4d4004ec..77cf6af47b 100644 --- a/plugins/search-backend-module-techdocs/src/alpha.ts +++ b/plugins/search-backend-module-techdocs/src/alpha.ts @@ -23,8 +23,8 @@ import { coreServices, createBackendModule, createExtensionPoint, + readSchedulerServiceTaskScheduleDefinitionFromConfig, } from '@backstage/backend-plugin-api'; -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { DefaultTechDocsCollatorFactory, @@ -101,7 +101,7 @@ export default createBackendModule({ }; const schedule = config.has('search.collators.techdocs.schedule') - ? readTaskScheduleDefinitionFromConfig( + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( config.getConfig('search.collators.techdocs.schedule'), ) : defaultSchedule; diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index 03269c72e8..4e769d0eae 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -16,9 +16,9 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { default as lunr_2 } from 'lunr'; import { Permission } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; +import { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { TaskFunction } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; import { Transform } from 'stream'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Writable } from 'stream'; @@ -158,7 +158,7 @@ export type QueryTranslator = (query: SearchQuery) => unknown; // @public export interface RegisterCollatorParameters { factory: DocumentCollatorFactory; - schedule: TaskRunner; + schedule: SchedulerServiceTaskRunner; } // @public @@ -177,8 +177,8 @@ export class Scheduler { // @public export type ScheduleTaskParameters = { id: string; - task: TaskFunction; - scheduledRunner: TaskRunner; + task: SchedulerServiceTaskFunction; + scheduledRunner: SchedulerServiceTaskRunner; }; // @public diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index a97a0a0a7c..29b3707515 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -55,7 +55,6 @@ "dependencies": { "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index 6796c7c01e..d34a3ef567 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { DocumentCollatorFactory, DocumentDecoratorFactory, @@ -54,12 +57,14 @@ class DifferentlyTypedDocumentDecoratorFactory extends TestDocumentDecoratorFact describe('IndexBuilder', () => { let testSearchEngine: SearchEngine; let testIndexBuilder: IndexBuilder; - let testScheduledTaskRunner: TaskRunner; + let testScheduledTaskRunner: SchedulerServiceTaskRunner; beforeEach(() => { const logger = mockServices.logger.mock(); testScheduledTaskRunner = { - run: async (task: TaskInvocationDefinition & { fn: () => void }) => { + run: async ( + task: SchedulerServiceTaskInvocationDefinition & { fn: () => void }, + ) => { task.fn(); }, }; diff --git a/plugins/search-backend-node/src/Scheduler.ts b/plugins/search-backend-node/src/Scheduler.ts index 7775db086c..3f56004d78 100644 --- a/plugins/search-backend-node/src/Scheduler.ts +++ b/plugins/search-backend-node/src/Scheduler.ts @@ -14,12 +14,15 @@ * limitations under the License. */ -import { TaskFunction, TaskRunner } from '@backstage/backend-tasks'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskFunction, +} from '@backstage/backend-plugin-api'; type TaskEnvelope = { - task: TaskFunction; - scheduledRunner: TaskRunner; + task: SchedulerServiceTaskFunction; + scheduledRunner: SchedulerServiceTaskRunner; }; /** @@ -28,8 +31,8 @@ type TaskEnvelope = { */ export type ScheduleTaskParameters = { id: string; - task: TaskFunction; - scheduledRunner: TaskRunner; + task: SchedulerServiceTaskFunction; + scheduledRunner: SchedulerServiceTaskRunner; }; /** diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts index 18853d1069..66ff2e491e 100644 --- a/plugins/search-backend-node/src/types.ts +++ b/plugins/search-backend-node/src/types.ts @@ -17,8 +17,8 @@ import { BackstageCredentials, LoggerService, + SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; -import { TaskRunner } from '@backstage/backend-tasks'; import { DocumentCollatorFactory, DocumentDecoratorFactory, @@ -43,9 +43,9 @@ export type IndexBuilderOptions = { export interface RegisterCollatorParameters { /** * The schedule for which the provided collator will be called, commonly the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} */ - schedule: TaskRunner; + schedule: SchedulerServiceTaskRunner; /** * The class responsible for returning the document collator of the given type. */ diff --git a/yarn.lock b/yarn.lock index 5c13778196..02e8341b26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3465,7 +3465,6 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" @@ -3715,7 +3714,6 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" @@ -3785,7 +3783,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/backend-tasks@workspace:^, @backstage/backend-tasks@workspace:packages/backend-tasks": +"@backstage/backend-tasks@workspace:packages/backend-tasks": version: 0.0.0-use.local resolution: "@backstage/backend-tasks@workspace:packages/backend-tasks" dependencies: @@ -5292,7 +5290,6 @@ __metadata: "@aws-sdk/util-stream-node": ^3.350.0 "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5318,7 +5315,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-azure@workspace:plugins/catalog-backend-module-azure" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -5338,7 +5334,6 @@ __metadata: dependencies: "@backstage/backend-openapi-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -5358,7 +5353,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -5381,7 +5375,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-bitbucket-server@workspace:plugins/catalog-backend-module-bitbucket-server" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5402,7 +5395,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-gcp@workspace:plugins/catalog-backend-module-gcp" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5418,7 +5410,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-gerrit@workspace:plugins/catalog-backend-module-gerrit" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -5439,7 +5430,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-github-org@workspace:plugins/catalog-backend-module-github-org" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -5456,7 +5446,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -5486,7 +5475,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-catalog-backend-module-gitlab": "workspace:^" @@ -5503,7 +5491,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5531,7 +5518,6 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5556,7 +5542,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-ldap@workspace:plugins/catalog-backend-module-ldap" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -5591,7 +5576,6 @@ __metadata: dependencies: "@azure/identity": ^4.0.0 "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5638,7 +5622,6 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-puppetdb@workspace:plugins/catalog-backend-module-puppetdb" dependencies: "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5696,7 +5679,6 @@ __metadata: "@backstage/backend-defaults": "workspace:^" "@backstage/backend-openapi-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -6140,7 +6122,6 @@ __metadata: "@aws-sdk/types": ^3.347.0 "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -7179,7 +7160,6 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -7430,7 +7410,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -7477,7 +7456,6 @@ __metadata: "@backstage-community/plugin-explore-common": ^0.0.4 "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -7512,7 +7490,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -7530,7 +7507,6 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -7556,7 +7532,6 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -27105,7 +27080,6 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -27168,7 +27142,6 @@ __metadata: dependencies: "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-app-backend": "workspace:^" From 148aed8d329ad4cfd4185c5abc4acfc6012ce5bf Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 19 Aug 2024 15:43:26 +0200 Subject: [PATCH 358/393] refactor: apply review suggestions Signed-off-by: Camila Belo --- packages/backend-tasks/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 7830df5bde..71659e5a6f 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -54,6 +54,5 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" - }, - "deprecated": true + } } From 4417dd40d1565e548a26caf544a756ab588a2e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jerna=C5=9B?= Date: Mon, 19 Aug 2024 16:14:20 +0200 Subject: [PATCH 359/393] Fix typo and unify TechDocs casing in doc strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Łukasz Jernaś --- .changeset/hip-pandas-think.md | 5 +++++ plugins/techdocs-node/src/extensions.ts | 16 ++++++++-------- plugins/techdocs-node/src/helpers.ts | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 .changeset/hip-pandas-think.md diff --git a/.changeset/hip-pandas-think.md b/.changeset/hip-pandas-think.md new file mode 100644 index 0000000000..a43349db63 --- /dev/null +++ b/.changeset/hip-pandas-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Fix typo and unify TechDocs casing in doc strings diff --git a/plugins/techdocs-node/src/extensions.ts b/plugins/techdocs-node/src/extensions.ts index c32a0d2db5..e9a9b1d731 100644 --- a/plugins/techdocs-node/src/extensions.ts +++ b/plugins/techdocs-node/src/extensions.ts @@ -25,7 +25,7 @@ import { import * as winston from 'winston'; /** - * Extension point type for configuring Techdocs builds. + * Extension point type for configuring TechDocs builds. * * @public */ @@ -35,7 +35,7 @@ export interface TechdocsBuildsExtensionPoint { } /** - * Extension point for configuring Techdocs builds. + * Extension point for configuring TechDocs builds. * * @public */ @@ -45,7 +45,7 @@ export const techdocsBuildsExtensionPoint = }); /** - * Extension point type for configuring a custom Techdocs generator + * Extension point type for configuring a custom TechDocs generator * * @public */ @@ -54,7 +54,7 @@ export interface TechdocsGeneratorExtensionPoint { } /** - * Extension point for configuring a custom Techdocs generator + * Extension point for configuring a custom TechDocs generator * * @public */ @@ -64,7 +64,7 @@ export const techdocsGeneratorExtensionPoint = }); /** - * Extension point type for configuring a custom Techdocs preparer + * Extension point type for configuring a custom TechDocs preparer * * @public */ @@ -73,7 +73,7 @@ export interface TechdocsPreparerExtensionPoint { } /** - * Extension point for configuring a custom Techdocs preparer + * Extension point for configuring a custom TechDocs preparer * * @public */ @@ -83,7 +83,7 @@ export const techdocsPreparerExtensionPoint = }); /** - * Extension point type for configuring a custom Techdocs publisher + * Extension point type for configuring a custom TechDocs publisher * * @public */ @@ -92,7 +92,7 @@ export interface TechdocsPublisherExtensionPoint { } /** - * Extension point for configuring a custom Techdocs publisher + * Extension point for configuring a custom TechDocs publisher * * @public */ diff --git a/plugins/techdocs-node/src/helpers.ts b/plugins/techdocs-node/src/helpers.ts index 38913e8e9b..d4816e1f72 100644 --- a/plugins/techdocs-node/src/helpers.ts +++ b/plugins/techdocs-node/src/helpers.ts @@ -38,7 +38,7 @@ export type ParsedLocationAnnotation = { }; /** - * Returns a parset locations annotation + * Returns a parsed locations annotation * @public * @param annotationName - The name of the annotation in the entity metadata * @param entity - A TechDocs entity instance @@ -114,7 +114,7 @@ export const transformDirLocation = ( }; /** - * Returns a entity reference based on the TechDocs annotation type + * Returns an entity reference based on the TechDocs annotation type * @public * @param entity - A TechDocs instance * @param scmIntegration - An implementation for SCM integration API From 8d2dbc6113d68e4efdf29c0ccb5af6df73ec0924 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 16:35:03 +0200 Subject: [PATCH 360/393] docs: add missing frontend-system migrations to sidebar Signed-off-by: Patrik Oldsberg --- microsite/sidebars.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 1a95b5a492..1c1bb8df81 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -460,7 +460,8 @@ "frontend-system/architecture/references", "frontend-system/architecture/utility-apis", "frontend-system/architecture/routes", - "frontend-system/architecture/naming-patterns" + "frontend-system/architecture/naming-patterns", + "frontend-system/architecture/migrations" ] }, { From 40ab9e91393d571a8aa8008a7a012f339cd04727 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Tue, 20 Aug 2024 11:29:33 +0530 Subject: [PATCH 361/393] Remove vacation note from readme doc Signed-off-by: AmbrishRamachandiran --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 6c27d01db2..4cba1082b0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ [![headline](docs/assets/headline.png)](https://backstage.io/) -> [!NOTE] -> 🏖 From July 1st through 16th, due to maintainers being on summer vacations, expect the project to move a little slower than normal, and support to be limited. Normal service will resume after that! 🏝 - # [Backstage](https://backstage.io) English \| [한국어](README-ko_kr.md) \| [中文版](README-zh_Hans.md) \| [Français](README-fr_FR.md) From 7b975ba628f2f01913de688af0c52644048cdfdb Mon Sep 17 00:00:00 2001 From: Oskar Jiang Date: Tue, 20 Aug 2024 08:17:51 +0200 Subject: [PATCH 362/393] techdocs-backend: use separate plugin request token when syncing cache Signed-off-by: Oskar Jiang --- plugins/techdocs-backend/src/service/router.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index b6f95d4e1b..fff55a9ed1 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -252,10 +252,14 @@ export async function createRouter( // However, if caching is enabled, take the opportunity to check and // invalidate stale cache entries. if (cache) { + const { token: techDocsToken } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'techdocs', + }); await docsSynchronizer.doCacheSync({ responseHandler, discovery, - token, + token: techDocsToken, entity, }); return; From 086c32dc5c87a882ca3672972a35da9e9c52c3b1 Mon Sep 17 00:00:00 2001 From: Oskar Jiang Date: Tue, 20 Aug 2024 08:19:44 +0200 Subject: [PATCH 363/393] chore: add changeset Signed-off-by: Oskar Jiang --- .changeset/big-spies-stare.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/big-spies-stare.md diff --git a/.changeset/big-spies-stare.md b/.changeset/big-spies-stare.md new file mode 100644 index 0000000000..bab1643284 --- /dev/null +++ b/.changeset/big-spies-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Dedicated token for techdocs cache sync From 1ca02516eaf7874e0fede69dc1bb9ed2e443386f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20=28Gentle=29=20Y=E6=9D=A8?= Date: Tue, 20 Aug 2024 14:29:21 +0800 Subject: [PATCH 364/393] Update README-zh_Hans.md to delete invalid characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Chris (Gentle) Y杨 --- README-zh_Hans.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-zh_Hans.md b/README-zh_Hans.md index 2813ee0793..129c7e7aa4 100644 --- a/README-zh_Hans.md +++ b/README-zh_Hans.md @@ -55,7 +55,7 @@ Backstage 的文档包括: - [Discord 聊天室](https://discord.gg/backstage-687207715902193673) - 获得支持或讨论项目 - [参与贡献 Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - 如果您想做出贡献,请从这里开始 - [RFCs](https://github.com/backstage/backstage/labels/rfc) - 帮助制定技术方向 -- [FAQ](https://backstage.io/docs/FAQ) - n.: 常问问题 +- [FAQ](https://backstage.io/docs/FAQ) - 常问问题 - [行为准则](CODE_OF_CONDUCT.md) - 这是我们的行事方式 - [采纳者](ADOPTERS.md) - 已经在使用 Backstage 的公司 - [博客](https://backstage.io/blog/) - 公告和更新 From 43b950a2ea932d44ccb92b1ac6d655c5c6dd31e6 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 20 Aug 2024 09:25:35 +0200 Subject: [PATCH 365/393] chore: use existing api for starred entities Signed-off-by: blam --- plugins/catalog-react/api-report.md | 1 - .../apis/StarredEntitiesApi/MockStarredEntitiesApi.ts | 4 ---- .../components/FavoriteEntity/FavoriteEntity.test.tsx | 10 ++++------ 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index b440701057..79aad53c33 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -686,7 +686,6 @@ export function MockEntityListContextProvider< // @public export class MockStarredEntitiesApi implements StarredEntitiesApi { - constructor(opts?: { starredEntities?: string[] }); // (undocumented) starredEntitie$(): Observable>; // (undocumented) diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts index ed60051026..9467153dd9 100644 --- a/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/MockStarredEntitiesApi.ts @@ -29,10 +29,6 @@ export class MockStarredEntitiesApi implements StarredEntitiesApi { ZenObservable.SubscriptionObserver> >(); - constructor(opts: { starredEntities?: string[] } = {}) { - this.starredEntities = new Set(opts.starredEntities); - } - private readonly observable = new ObservableImpl>(subscriber => { subscriber.next(new Set(this.starredEntities)); diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx index a166d0a15d..d36700452a 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx @@ -73,16 +73,14 @@ describe('', () => { }); it('should remove from favorites', async () => { + const starredEntities = new MockStarredEntitiesApi(); + await starredEntities.toggleStarred('component:default/example'); + await renderInTestApp( From 2ae63cd7a700aa45157cb69cee7af4de19d1c95f Mon Sep 17 00:00:00 2001 From: mario ma Date: Tue, 30 Jul 2024 11:23:25 +0800 Subject: [PATCH 366/393] feat: add i18n for scaffolder Signed-off-by: mario ma --- .changeset/many-years-lie.md | 5 + plugins/scaffolder/src/alpha.tsx | 2 + .../components/ActionsPage/ActionsPage.tsx | 34 ++- .../ListTasksPage/ListTasksPage.tsx | 26 +- .../ListTasksPage/OwnerListPicker.tsx | 18 +- .../components/OngoingTask/ContextMenu.tsx | 21 +- .../components/OngoingTask/OngoingTask.tsx | 21 +- .../TemplateTypePicker.test.tsx | 6 +- .../TemplateTypePicker/TemplateTypePicker.tsx | 5 +- .../EntityNamePicker/EntityNamePicker.tsx | 8 +- .../fields/EntityPicker/EntityPicker.tsx | 8 +- .../EntityTagsPicker/EntityTagsPicker.tsx | 10 +- .../MyGroupsPicker/MyGroupsPicker.test.tsx | 12 +- .../fields/MyGroupsPicker/MyGroupsPicker.tsx | 8 +- .../OwnedEntityPicker/OwnedEntityPicker.tsx | 8 +- .../fields/OwnerPicker/OwnerPicker.tsx | 8 +- .../RepoUrlPicker/AzureRepoPicker.test.tsx | 13 +- .../fields/RepoUrlPicker/AzureRepoPicker.tsx | 65 ++-- .../BitbucketRepoPicker.test.tsx | 34 +-- .../RepoUrlPicker/BitbucketRepoPicker.tsx | 24 +- .../RepoUrlPicker/GerritRepoPicker.test.tsx | 11 +- .../fields/RepoUrlPicker/GerritRepoPicker.tsx | 13 +- .../RepoUrlPicker/GiteaRepoPicker.test.tsx | 7 +- .../fields/RepoUrlPicker/GiteaRepoPicker.tsx | 41 +-- .../RepoUrlPicker/GithubRepoPicker.test.tsx | 13 +- .../fields/RepoUrlPicker/GithubRepoPicker.tsx | 34 ++- .../RepoUrlPicker/GitlabRepoPicker.test.tsx | 13 +- .../fields/RepoUrlPicker/GitlabRepoPicker.tsx | 41 +-- .../RepoUrlPicker/RepoUrlPickerHost.tsx | 7 +- .../RepoUrlPickerRepoName.test.tsx | 11 +- .../RepoUrlPicker/RepoUrlPickerRepoName.tsx | 15 +- .../CustomFieldExplorer.tsx | 19 +- .../DryRunResults/DryRunResults.tsx | 5 +- .../DryRunResults/DryRunResultsList.tsx | 17 +- .../DryRunResults/DryRunResultsView.tsx | 18 +- .../DryRunResults/TaskStatusStepper.tsx | 9 +- .../TemplateEditorBrowser.tsx | 19 +- .../TemplateEditorIntro.tsx | 26 +- .../TemplateEditorPage/TemplateEditorPage.tsx | 7 +- .../TemplateEditorTextArea.tsx | 15 +- .../TemplateFormPreviewer.tsx | 7 +- .../TemplateListPage/TemplateListPage.tsx | 45 +-- .../TemplateWizardPage/TemplateWizardPage.tsx | 9 +- .../TemplateWizardPageContextMenu.tsx | 9 +- plugins/scaffolder/src/translation.ts | 286 ++++++++++++++++++ 45 files changed, 776 insertions(+), 257 deletions(-) create mode 100644 .changeset/many-years-lie.md create mode 100644 plugins/scaffolder/src/translation.ts diff --git a/.changeset/many-years-lie.md b/.changeset/many-years-lie.md new file mode 100644 index 0000000000..8615ddba21 --- /dev/null +++ b/.changeset/many-years-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +add i18n for scaffolder diff --git a/plugins/scaffolder/src/alpha.tsx b/plugins/scaffolder/src/alpha.tsx index 3136626f47..5b7353053e 100644 --- a/plugins/scaffolder/src/alpha.tsx +++ b/plugins/scaffolder/src/alpha.tsx @@ -106,3 +106,5 @@ export default createFrontendPlugin({ }), extensions: [scaffolderApi, scaffolderPage, scaffolderNavItem], }); + +export * from './translation'; \ No newline at end of file diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 7e859e272d..2f9bcba279 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -58,6 +58,8 @@ import { rootRouteRef, scaffolderListTaskRouteRef, } from '../../routes'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ code: { @@ -115,6 +117,7 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => { const ActionPageContent = () => { const api = useApi(scaffolderApiRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const classes = useStyles(); const { loading, value, error } = useAsync(async () => { @@ -132,8 +135,8 @@ const ActionPageContent = () => { ); @@ -141,17 +144,21 @@ const ActionPageContent = () => { const renderTable = (rows?: JSX.Element[]) => { if (!rows || rows.length < 1) { - return No schema defined; + return ( + {t('actionsPage.content.noRowsDescription')} + ); } return ( - Name - Title - Description - Type + {t('actionsPage.content.tableCell.name')} + {t('actionsPage.content.tableCell.title')} + + {t('actionsPage.content.tableCell.description')} + + {t('actionsPage.content.tableCell.type')} {rows} @@ -295,7 +302,7 @@ const ActionPageContent = () => { {action.schema?.input && ( - Input + {t('actionsPage.action.input')} {renderTable( formatRows(`${action.id}.input`, action?.schema?.input), @@ -306,7 +313,7 @@ const ActionPageContent = () => { {action.schema?.output && ( - Output + {t('actionsPage.action.output')} {renderTable( formatRows(`${action.id}.output`, action?.schema?.output), @@ -317,7 +324,7 @@ const ActionPageContent = () => { }> - Examples + {t('actionsPage.action.examples')} @@ -336,6 +343,7 @@ export const ActionsPage = () => { const editorLink = useRouteRef(editRouteRef); const tasksLink = useRouteRef(scaffolderListTaskRouteRef); const createLink = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), @@ -347,9 +355,9 @@ export const ActionsPage = () => { return (
    diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index d37716a710..7bc34662ef 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -41,6 +41,8 @@ import { import { actionsRouteRef, editRouteRef, rootRouteRef } from '../../routes'; import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; import { useNavigate } from 'react-router-dom'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; export interface MyTaskPageProps { initiallySelectedFilter?: 'owned' | 'all'; @@ -48,6 +50,7 @@ export interface MyTaskPageProps { const ListTaskPageContent = (props: MyTaskPageProps) => { const { initiallySelectedFilter = 'owned' } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderApi = useApi(scaffolderApiRef); const rootLink = useRouteRef(rootRouteRef); @@ -76,8 +79,8 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { ); @@ -94,17 +97,17 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { data={value?.tasks ?? []} - title="Tasks" + title={t('listTaskPage.content.tableTitle')} columns={[ { - title: 'Task ID', + title: t('listTaskPage.content.tableCell.taskID'), field: 'id', render: row => ( {row.id} ), }, { - title: 'Template', + title: t('listTaskPage.content.tableCell.template'), field: 'spec.templateInfo.entity.metadata.title', render: row => ( { ), }, { - title: 'Created', + title: t('listTaskPage.content.tableCell.created'), field: 'createdAt', render: row => , }, { - title: 'Owner', + title: t('listTaskPage.content.tableCell.owner'), field: 'createdBy', render: row => ( ), }, { - title: 'Status', + title: t('listTaskPage.content.tableCell.status'), field: 'status', render: row => , }, @@ -141,6 +144,7 @@ export const ListTasksPage = (props: MyTaskPageProps) => { const editorLink = useRouteRef(editRouteRef); const actionsLink = useRouteRef(actionsRouteRef); const createLink = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), @@ -151,9 +155,9 @@ export const ListTasksPage = (props: MyTaskPageProps) => { return (
    diff --git a/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx b/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx index d2489303d3..00efed45ba 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx @@ -23,6 +23,11 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import SettingsIcon from '@material-ui/icons/Settings'; import React, { Fragment } from 'react'; +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; import AllIcon from '@material-ui/icons/FontDownload'; @@ -64,19 +69,21 @@ export type ButtonGroup = { }[]; }; -function getFilterGroups(): ButtonGroup[] { +function getFilterGroups( + t: TranslationFunction, +): ButtonGroup[] { return [ { - name: 'Task Owner', + name: t('ownerListPicker.title'), items: [ { id: 'owned', - label: 'Owned', + label: t('ownerListPicker.options.owned'), icon: SettingsIcon, }, { id: 'all', - label: 'All', + label: t('ownerListPicker.options.all'), icon: AllIcon, }, ], @@ -90,8 +97,9 @@ export const OwnerListPicker = (props: { }) => { const { filter, onSelectOwner } = props; const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); - const filterGroups = getFilterGroups(); + const filterGroups = getFilterGroups(t); return ( {filterGroups.map(group => ( diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index f96f83fd2f..e0ab01fc67 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -36,6 +36,8 @@ import { taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; type ContextMenuProps = { cancelEnabled?: boolean; @@ -69,6 +71,7 @@ export const ContextMenu = (props: ContextMenuProps) => { const scaffolderApi = useApi(scaffolderApiRef); const analytics = useAnalytics(); const [anchorEl, setAnchorEl] = useState(); + const { t } = useTranslationRef(scaffolderTranslationRef); const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => { if (taskId) { @@ -118,14 +121,24 @@ export const ContextMenu = (props: ContextMenuProps) => { - + onToggleButtonBar?.(!buttonBarVisible)}> { - + { - + diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 459ba682d2..b956e27aca 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -42,6 +42,8 @@ import { taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -85,6 +87,7 @@ export const OngoingTask = (props: { })) ?? [], [taskStream], ); + const { t } = useTranslationRef(scaffolderTranslationRef); const [logsVisible, setLogVisibleState] = useState(false); const [buttonBarVisible, setButtonBarVisibleState] = useState(true); @@ -166,7 +169,7 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const templateName = - taskStream.task?.spec.templateInfo?.entity?.metadata.name; + taskStream.task?.spec.templateInfo?.entity?.metadata.name || ''; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); @@ -174,14 +177,16 @@ export const OngoingTask = (props: {
    - Run of {templateName} + {t('ongoingTask.title')} {templateName} } - subtitle={`Task ${taskId}`} + subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })} > - Cancel + {t('ongoingTask.cancelButtonTitle')} diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx index aa6630d4aa..4c4b88e471 100644 --- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx +++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/plugin-catalog-react'; import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ @@ -86,7 +86,7 @@ const apis = TestApiRegistry.from( describe('', () => { it('renders available entity types', async () => { - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ', () => { }); it('sets the selected type filters', async () => { - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ; const checkedIcon = ; @@ -41,6 +43,7 @@ export const TemplateTypePicker = () => { const alertApi = useApi(alertApiRef); const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter(); + const { t } = useTranslationRef(scaffolderTranslationRef); if (loading) return ; @@ -61,7 +64,7 @@ export const TemplateTypePicker = () => { component="label" htmlFor="categories-picker" > - Categories + {t('templateTypePicker.title')} id="categories-picker" diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 9f49fc2b94..c219ae0d63 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { EntityNamePickerProps } from './schema'; import TextField from '@material-ui/core/TextField'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityNamePickerSchema } from './schema'; @@ -23,10 +25,14 @@ export { EntityNamePickerSchema } from './schema'; * EntityName Picker */ export const EntityNamePicker = (props: EntityNamePickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, required, - schema: { title = 'Name', description = 'Unique name of the component' }, + schema: { + title = t('fields.entityNamePicker.title'), + description = t('fields.entityNamePicker.description'), + }, rawErrors, formData, uiSchema: { 'ui:autofocus': autoFocus }, diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 9eca3e85c5..b97b776b19 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -44,6 +44,8 @@ import { EntityPickerFilterQuery, } from './schema'; import { VirtualizedListbox } from '../VirtualizedListbox'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityPickerSchema } from './schema'; @@ -54,9 +56,13 @@ export { EntityPickerSchema } from './schema'; * @public */ export const EntityPicker = (props: EntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.entityPicker.title'), + description = t('fields.entityPicker.description'), + }, required, uiSchema, rawErrors, diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index bf95a08c85..25b5d926a5 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -24,6 +24,8 @@ import FormControl from '@material-ui/core/FormControl'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { EntityTagsPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityTagsPickerSchema } from './schema'; @@ -43,6 +45,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { const kinds = uiSchema['ui:options']?.kinds; const showCounts = uiSchema['ui:options']?.showCounts; const helperText = uiSchema['ui:options']?.helperText; + const { t } = useTranslationRef(scaffolderTranslationRef); const { loading, value: existingTags } = useAsync(async () => { const facet = 'metadata.tags'; @@ -109,13 +112,10 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { renderInput={params => ( setInputValue(e.target.value)} error={inputError} - helperText={ - helperText ?? - "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters" - } + helperText={helperText ?? t('fields.entityTagsPicker.description')} FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} /> )} diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx index 4e54ee99ad..74af578a21 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx @@ -15,10 +15,10 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; import { CatalogApi } from '@backstage/catalog-client'; import { MyGroupsPicker } from './MyGroupsPicker'; -import { TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { catalogApiRef, entityPresentationApiRef, @@ -115,7 +115,7 @@ describe('', () => { required, } as unknown as FieldProps; - render( + await renderInTestApp( ', () => { required, } as unknown as FieldProps; - const { queryByText, getByRole } = render( + const { queryByText, getByRole } = await renderInTestApp( ', () => { required, } as unknown as FieldProps; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( ', () => { formData: 'group:default/group1', } as unknown as FieldProps; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title, description }, + schema: { + title = t('fields.myGroupsPicker.title'), + description = t('fields.myGroupsPicker.description'), + }, required, rawErrors, onChange, diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 02c437ceb6..06fdf17824 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -23,6 +23,8 @@ import { EntityPicker } from '../EntityPicker/EntityPicker'; import { OwnedEntityPickerProps } from './schema'; import { EntityPickerProps } from '../EntityPicker/schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { OwnedEntityPickerSchema } from './schema'; @@ -33,8 +35,12 @@ export { OwnedEntityPickerSchema } from './schema'; * @public */ export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.ownedEntityPicker.title'), + description = t('fields.ownedEntityPicker.description'), + }, uiSchema, required, } = props; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 5c69e73e12..792685f040 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { EntityPicker } from '../EntityPicker/EntityPicker'; import { OwnerPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { OwnerPickerSchema } from './schema'; @@ -26,8 +28,12 @@ export { OwnerPickerSchema } from './schema'; * @public */ export const OwnerPicker = (props: OwnerPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title = 'Owner', description = 'The owner of the component' }, + schema: { + title = t('fields.ownerPicker.title'), + description = t('fields.ownerPicker.description'), + }, uiSchema, ...restProps } = props; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx index 0380ac6ce8..904fb615eb 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx @@ -16,11 +16,12 @@ import React from 'react'; import { AzureRepoPicker } from './AzureRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('AzureRepoPicker', () => { it('renders the two input fields', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); @@ -30,9 +31,9 @@ describe('AzureRepoPicker', () => { }); describe('org field', () => { - it('calls onChange when the organisation changes', () => { + it('calls onChange when the organisation changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); @@ -45,9 +46,9 @@ describe('AzureRepoPicker', () => { }); describe('project field', () => { - it('calls onChange when the project changes', () => { + it('calls onChange when the project changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx index c7b0f1f887..59e666482a 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -20,6 +20,8 @@ import FormHelperText from '@material-ui/core/FormHelperText'; import TextField from '@material-ui/core/TextField'; import { BaseRepoUrlPickerProps } from './types'; import { Select, SelectItem } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export const AzureRepoPicker = ( props: BaseRepoUrlPickerProps<{ @@ -34,6 +36,7 @@ export const AzureRepoPicker = ( state, onChange, } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const organizationItems: SelectItem[] = allowedOrganizations ? allowedOrganizations.map(i => ({ label: i, value: i })) @@ -53,27 +56,30 @@ export const AzureRepoPicker = ( error={rawErrors?.length > 0 && !organization} > {allowedOrganizations?.length ? ( - + onChange({ organization: String(Array.isArray(s) ? s[0] : s) }) + } + disabled={allowedOrganizations.length === 1} + selected={organization} + items={organizationItems} + /> + + {t('fields.azureRepoPicker.organization.description')} + + ) : ( onChange({ organization: e.target.value })} + helperText={t('fields.azureRepoPicker.organization.description')} value={organization} /> )} - - The Organization that this repo will belong to - 0 && !project} > {allowedProject?.length ? ( - + onChange({ project: String(Array.isArray(s) ? s[0] : s) }) + } + disabled={allowedProject.length === 1} + selected={project} + items={projectItems} + /> + + {t('fields.azureRepoPicker.project.description')} + + ) : ( onChange({ project: e.target.value })} value={project} + helperText={t('fields.azureRepoPicker.project.description')} /> )} - - The Project that this repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx index 570c5bd664..05d04886e4 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx @@ -16,9 +16,9 @@ import React from 'react'; import { BitbucketRepoPicker } from './BitbucketRepoPicker'; -import { render, fireEvent, waitFor } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { ScaffolderApi, scaffolderApiRef, @@ -36,7 +36,7 @@ describe('BitbucketRepoPicker', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { expect(await findByText('owner2')).toBeInTheDocument(); }); - it('renders workspace input when host is bitbucket.org', () => { + it('renders workspace input when host is bitbucket.org', async () => { const state = { host: 'bitbucket.org', workspace: 'lolsWorkspace' }; - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { expect(getAllByRole('textbox')[0]).toHaveValue('lolsWorkspace'); }); - it('hides the workspace input when the host is not bitbucket.org', () => { + it('hides the workspace input when the host is not bitbucket.org', async () => { const state = { host: 'mycustom.domain.bitbucket.org', }; - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); describe('workspace field', () => { - it('calls onChange when the workspace changes', () => { + it('calls onChange when the workspace changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); describe('project field', () => { - it('calls onChange when the project changes', () => { + it('calls onChange when the project changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); it('Does not render a select if the list of allowed projects does not exist', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); it('Does not render a select if the list of allowed projects is empty', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { it('Does render a select if there is a list of allowed projects', async () => { const allowedProjects = ['project1', 'project2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('should populate workspaces if host is set and accessToken is provided', async () => { const onChange = jest.fn(); - const { getAllByRole, getByText } = render( + const { getAllByRole, getByText } = await renderInTestApp( { it('should populate projects if host and workspace are set and accessToken is provided', async () => { const onChange = jest.fn(); - const { getAllByRole, getByText } = render( + const { getAllByRole, getByText } = await renderInTestApp( { it('should populate repositories if host, workspace and project are set and accessToken is provided', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( ({ label: i, value: i })) @@ -164,7 +168,7 @@ export const BitbucketRepoPicker = ( {allowedOwners?.length ? ( onChange({ project: String(Array.isArray(s) ? s[0] : s) }) } @@ -215,14 +223,18 @@ export const BitbucketRepoPicker = ( }} options={availableProjects} renderInput={params => ( - + )} freeSolo autoSelect /> )} - The Project that this repo will belong to + {t('fields.bitbucketRepoPicker.project.description')} diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx index f51d00adf2..f00e286418 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GerritRepoPicker } from './GerritRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GerritRepoPicker', () => { describe('owner input field', () => { - it('calls onChange when the owner input changes', () => { + it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); describe('parent field', () => { - it('calls onChange when the parent changes', () => { + it('calls onChange when the parent changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { const { onChange, rawErrors, state } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const { workspace, owner } = state; return ( <> 0 && !workspace}> onChange({ owner: e.target.value })} + helperText={t('fields.gerritRepoPicker.owner.description')} value={owner} /> - The owner of the project (optional) { > onChange({ workspace: e.target.value })} value={workspace} + helperText={t('fields.gerritRepoPicker.parent.description')} /> - - The project parent that the repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx index e66ca49af1..056d8f0d11 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GiteaRepoPicker } from './GiteaRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GiteaRepoPicker', () => { describe('owner input field', () => { - it('calls onChange when the owner input changes', () => { + it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], state, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -41,32 +44,36 @@ export const GiteaRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ + owner: String( + Array.isArray(selected) ? selected[0] : selected, + ), + }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.giteaRepoPicker.owner.description')} + + ) : ( <> onChange({ owner: e.target.value })} + helperText={t('fields.giteaRepoPicker.owner.description')} value={owner} /> )} - - Gitea namespace where this repository will belong to. It can be the - name of organization, group, subgroup, user, or the project. - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx index 63bc5b8013..3a01c6de7c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GithubRepoPicker } from './GithubRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GithubRepoPicker', () => { describe('owner field', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('calls onChange when the owner is changed to a different owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1', 'owner2']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); }); - it('is disabled picked when only one allowed owner', () => { + it('is disabled picked when only one allowed owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { it('should display free text if no allowed owners are passed', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], rawErrors, state, onChange } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -40,27 +43,30 @@ export const GithubRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ owner: String(Array.isArray(s) ? s[0] : s) }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.githubRepoPicker.owner.description')} + + ) : ( onChange({ owner: e.target.value })} + helperText={t('fields.githubRepoPicker.owner.description')} value={owner} /> )} - - The organization, user or project that this repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx index ad5c7f765e..f3d6e2da91 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GitlabRepoPicker } from './GitlabRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GitlabRepoPicker', () => { describe('owner field', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('calls onChange when the owner is changed to a different owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1', 'owner2']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); }); - it('is disabled picked when only one allowed owner', () => { + it('is disabled picked when only one allowed owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { it('should display free text if no allowed owners are passed', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], state, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -41,30 +44,34 @@ export const GitlabRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ + owner: String( + Array.isArray(selected) ? selected[0] : selected, + ), + }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.gitlabRepoPicker.owner.description')} + + ) : ( onChange({ owner: e.target.value })} + helperText={t('fields.gitlabRepoPicker.owner.description')} value={owner} /> )} - - GitLab namespace where this repository will belong to. It can be the - name of organization, group, subgroup, user, or the project. - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index ed06983f90..86adc5ee20 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -20,6 +20,8 @@ import FormHelperText from '@material-ui/core/FormHelperText'; import { useApi } from '@backstage/core-plugin-api'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import useAsync from 'react-use/esm/useAsync'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export const RepoUrlPickerHost = (props: { host?: string; @@ -28,6 +30,7 @@ export const RepoUrlPickerHost = (props: { rawErrors: string[]; }) => { const { host, hosts, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderApi = useApi(scaffolderApiRef); const { value: { integrations } = { integrations: [] }, loading } = useAsync( @@ -73,7 +76,7 @@ export const RepoUrlPickerHost = (props: { onChange(String(Array.isArray(selected) ? selected[0] : selected)) } @@ -69,13 +72,19 @@ export const RepoUrlPickerRepoName = (props: { }} options={availableRepos || []} renderInput={params => ( - + )} freeSolo autoSelect /> )} - The name of the repository + + {t('fields.repoUrlPickerRepoName.repository.description')} + ); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx index bddf891ec6..041ef9483f 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx @@ -33,6 +33,8 @@ import { Form } from '@backstage/plugin-scaffolder-react/alpha'; import { TemplateEditorForm } from './TemplateEditorForm'; import validator from '@rjsf/validator-ajv8'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ root: { @@ -68,6 +70,7 @@ export const CustomFieldExplorer = ({ onClose?: () => void; }) => { const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); const fieldOptions = customFieldExtensions.filter(field => !!field.schema); const [selectedField, setSelectedField] = useState(fieldOptions[0]); const [fieldFormState, setFieldFormState] = useState({}); @@ -120,11 +123,11 @@ export const CustomFieldExplorer = ({
    - Choose Custom Field Extension + {t('templateEditorPage.customFieldExplorer.selectFieldLabel')} handleSelectChange(e.target.value)} > diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index b1508e43fc..400bc9d192 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -52,6 +52,11 @@ import { } from '../../routes'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; /** * @alpha @@ -74,17 +79,13 @@ export type TemplateListPageProps = { }; }; -const defaultGroup: TemplateGroupFilter = { - title: 'Templates', - filter: () => true, -}; - const createGroupsWithOther = ( groups: TemplateGroupFilter[], + t: TranslationFunction, ): TemplateGroupFilter[] => [ ...groups, { - title: 'Other Templates', + title: t('templateListPage.templateGroups.otherTitle'), filter: e => ![...groups].some(({ filter }) => filter(e)), }, ]; @@ -107,10 +108,16 @@ export const TemplateListPage = (props: TemplateListPageProps) => { const viewTechDocsLink = useRouteRef(viewTechDocRouteRef); const templateRoute = useRouteRef(selectedTemplateRouteRef); const app = useApp(); + const { t } = useTranslationRef(scaffolderTranslationRef); const groups = givenGroups.length - ? createGroupsWithOther(givenGroups) - : [defaultGroup]; + ? createGroupsWithOther(givenGroups, t) + : [ + { + title: t('templateListPage.templateGroups.defaultTitle'), + filter: () => true, + }, + ]; const scaffolderPageContextMenuProps = { onEditorClicked: @@ -137,13 +144,15 @@ export const TemplateListPage = (props: TemplateListPageProps) => { ? [ { icon: app.getSystemIcon('docs') ?? DocsIcon, - text: 'View TechDocs', + text: t( + 'templateListPage.additionalLinksForEntity.viewTechDocsTitle', + ), url: viewTechDocsLink({ kind, namespace, name }), }, ] : []; }, - [app, viewTechDocsLink], + [app, viewTechDocsLink, t], ); const onTemplateSelected = useCallback( @@ -159,23 +168,23 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
    - + - Create new software components using standard templates. Different - templates create different kinds of components (services, - websites, documentation, ...). + {t('templateListPage.contentHeader.supportButtonTitle')} diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index aef6ca6bb1..0524b99f8f 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -45,6 +45,8 @@ import { scaffolderTaskRouteRef, selectedTemplateRouteRef, } from '../../routes'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; import { TemplateWizardPageContextMenu } from './TemplateWizardPageContextMenu'; @@ -75,6 +77,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const { templateName, namespace } = useRouteRefParams( selectedTemplateRouteRef, ); + const { t } = useTranslationRef(scaffolderTranslationRef); const templateRef = stringifyEntityRef({ kind: 'Template', @@ -103,9 +106,9 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
    diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx index c4927d5d14..50e406112a 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx @@ -24,6 +24,8 @@ import { makeStyles } from '@material-ui/core/styles'; import Edit from '@material-ui/icons/Edit'; import MoreVert from '@material-ui/icons/MoreVert'; import React, { useState } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ button: { @@ -41,6 +43,7 @@ export function TemplateWizardPageContextMenu( const { editUrl } = props; const classes = useStyles(); const [anchorEl, setAnchorEl] = useState(); + const { t } = useTranslationRef(scaffolderTranslationRef); if (!editUrl) { return null; @@ -83,7 +86,11 @@ export function TemplateWizardPageContextMenu( - + diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts new file mode 100644 index 0000000000..eb6e4d005a --- /dev/null +++ b/plugins/scaffolder/src/translation.ts @@ -0,0 +1,286 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const scaffolderTranslationRef = createTranslationRef({ + id: 'scaffolder', + messages: { + actionsPage: { + title: 'Installed actions', + pageTitle: 'Create a New Component', + subtitle: 'This is the collection of all installed actions', + content: { + emptyState: { + title: 'No information to display', + description: + 'There are no actions installed or there was an issue communicating with backend.', + }, + tableCell: { + name: 'Name', + title: 'Title', + description: 'Description', + type: 'Type', + }, + noRowsDescription: 'No schema defined', + }, + action: { + input: 'Input', + output: 'Output', + examples: 'Examples', + }, + }, + fields: { + entityNamePicker: { + title: 'Name', + description: 'Unique name of the component', + }, + entityPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, + entityTagsPicker: { + title: 'Tags', + description: + "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters", + }, + myGroupsPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, + ownedEntityPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, + ownerPicker: { + title: 'Owner', + description: 'The owner of the component', + }, + azureRepoPicker: { + organization: { + title: 'Organization', + description: 'The Organization that this repo will belong to', + }, + project: { + title: 'Project', + description: 'The Project that this repo will belong to', + }, + }, + bitbucketRepoPicker: { + workspaces: { + title: 'Allowed Workspaces', + inputTitle: 'Workspaces', + description: 'The Workspace that this repo will belong to', + }, + project: { + title: 'Allowed Projects', + inputTitle: 'Projects', + description: 'The Project that this repo will belong to', + }, + }, + gerritRepoPicker: { + owner: { + title: 'Owner', + description: 'The owner of the project (optional)', + }, + parent: { + title: 'Parent', + description: 'The project parent that the repo will belong to', + }, + }, + giteaRepoPicker: { + owner: { + title: 'Owner Available', + inputTitle: 'Owner', + description: + 'Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.', + }, + }, + githubRepoPicker: { + owner: { + title: 'Owner Available', + inputTitle: 'Owner', + description: + 'The organization, user or project that this repo will belong to', + }, + }, + gitlabRepoPicker: { + owner: { + title: 'Owner Available', + inputTitle: 'Owner', + description: + 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.', + }, + }, + repoUrlPickerHost: { + host: { + title: 'Host', + description: 'The host where the repository will be created', + }, + }, + repoUrlPickerRepoName: { + repository: { + title: 'Repositories Available', + inputTitle: 'Repository', + description: 'The name of the repository', + }, + }, + }, + listTaskPage: { + title: 'List template tasks', + pageTitle: 'Templates Tasks', + subtitle: 'All tasks that have been started', + content: { + emptyState: { + title: 'No information to display', + description: + 'There are no tasks or there was an issue communicating with backend.', + }, + tableTitle: 'Tasks', + tableCell: { + taskID: 'Task ID', + template: 'Template', + created: 'Created', + owner: 'Owner', + status: 'Status', + }, + }, + }, + ownerListPicker: { + title: 'Task Owner', + options: { + owned: 'Owned', + all: 'All', + }, + }, + ongoingTask: { + title: 'Run of', + pageTitle: { + hasTemplateName: 'Run of {{templateName}}', + noTemplateName: 'Scaffolder Run', + }, + subtitle: 'Task {{taskId}}', + cancelButtonTitle: 'Cancel', + startOverButtonTitle: 'Start Over', + hideLogsButtonTitle: 'Hide Logs', + showLogsButtonTitle: 'Show Logs', + contextMenu: { + hideLogs: 'Hide Logs', + showLogs: 'Show Logs', + hideButtonBar: 'Hide Button Bar', + showButtonBar: 'Show Button Bar', + startOver: 'Start Over', + cancel: 'Cancel', + }, + }, + templateTypePicker: { + title: 'Categories', + }, + templateEditorPage: { + title: 'Template Editor', + subTitle: 'Edit, preview, and try out templates and template forms', + dryRunResults: { + title: 'Dry-run results', + }, + dryRunResultsList: { + title: 'Result {{resultId}}', + downloadButtonTitle: 'Download as .zip', + deleteButtonTitle: 'Delete result', + }, + dryRunResultsView: { + tab: { + files: 'Files', + log: 'Log', + output: 'Output', + }, + }, + taskStatusStepper: { + skippedStepTitle: 'Skipped', + }, + customFieldExplorer: { + selectFieldLabel: 'Choose Custom Field Extension', + fieldForm: { + title: 'Field Options', + applyButtonTitle: 'Apply', + }, + preview: { + title: 'Example Template Spec', + }, + }, + templateEditorBrowser: { + closeConfirmMessage: 'Are you sure? Unsaved changes will be lost', + saveIconTooltip: 'Save all files', + reloadIconTooltip: 'Reload directory', + closeIconTooltip: 'Close directory', + }, + templateEditorIntro: { + title: 'Get started by choosing one of the options below', + loadLocal: { + title: 'Load Template Directory', + description: + 'Load a local template directory, allowing you to both edit and try executing your own template.', + unSupportsLoadTooltip: + 'Only supported in some Chromium-based browsers', + }, + formEditor: { + title: 'Edit Template Form', + description: + 'Preview and edit a template form, either using a sample template or by loading a template from the catalog.', + }, + fieldExplorer: { + title: 'Custom Field Explorer', + description: + 'View and play around with available installed custom field extensions.', + }, + }, + templateEditorTextArea: { + saveIconTooltip: 'Save file', + refreshIconTooltip: 'Reload file', + }, + templateFormPreviewer: { + title: 'Load Existing Template', + }, + }, + templateListPage: { + title: 'Create a new component', + subtitle: + 'Create new software components using standard templates in your organization', + pageTitle: 'Create a new component', + templateGroups: { + defaultTitle: 'Templates', + otherTitle: 'Other Templates', + }, + contentHeader: { + title: 'Available Templates', + registerExistingButtonTitle: 'Register Existing Component', + supportButtonTitle: + 'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).', + }, + additionalLinksForEntity: { + viewTechDocsTitle: 'View TechDocs', + }, + }, + templateWizardPage: { + title: 'Create a new component', + subtitle: + 'Create new software components using standard templates in your organization', + pageTitle: 'Create a new component', + pageContextMenu: { + editConfigurationTitle: 'Edit Configuration', + }, + }, + }, +}); From adc4890e7f7e390f7028e2feec1d9909e723eab5 Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 14 Aug 2024 14:37:19 +0800 Subject: [PATCH 367/393] fix: add api report alpha Signed-off-by: mario ma --- plugins/scaffolder/api-report-alpha.md | 132 +++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index 508fa17fb2..2316f9c80c 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -21,6 +21,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -110,6 +111,137 @@ export type FormProps = Pick< 'transformErrors' | 'noHtml5Validate' >; +// @alpha (undocumented) +export const scaffolderTranslationRef: TranslationRef< + 'scaffolder', + { + readonly 'fields.entityNamePicker.title': 'Name'; + readonly 'fields.entityNamePicker.description': 'Unique name of the component'; + readonly 'fields.entityPicker.title': 'Entity'; + readonly 'fields.entityPicker.description': 'An entity from the catalog'; + readonly 'fields.entityTagsPicker.title': 'Tags'; + readonly 'fields.entityTagsPicker.description': "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"; + readonly 'fields.myGroupsPicker.title': 'Entity'; + readonly 'fields.myGroupsPicker.description': 'An entity from the catalog'; + readonly 'fields.ownedEntityPicker.title': 'Entity'; + readonly 'fields.ownedEntityPicker.description': 'An entity from the catalog'; + readonly 'fields.ownerPicker.title': 'Owner'; + readonly 'fields.ownerPicker.description': 'The owner of the component'; + readonly 'fields.azureRepoPicker.organization.title': 'Organization'; + readonly 'fields.azureRepoPicker.organization.description': 'The Organization that this repo will belong to'; + readonly 'fields.azureRepoPicker.project.title': 'Project'; + readonly 'fields.azureRepoPicker.project.description': 'The Project that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.project.title': 'Allowed Projects'; + readonly 'fields.bitbucketRepoPicker.project.description': 'The Project that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.project.inputTitle': 'Projects'; + readonly 'fields.bitbucketRepoPicker.workspaces.title': 'Allowed Workspaces'; + readonly 'fields.bitbucketRepoPicker.workspaces.description': 'The Workspace that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.workspaces.inputTitle': 'Workspaces'; + readonly 'fields.gerritRepoPicker.parent.title': 'Parent'; + readonly 'fields.gerritRepoPicker.parent.description': 'The project parent that the repo will belong to'; + readonly 'fields.gerritRepoPicker.owner.title': 'Owner'; + readonly 'fields.gerritRepoPicker.owner.description': 'The owner of the project (optional)'; + readonly 'fields.giteaRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.giteaRepoPicker.owner.description': 'Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; + readonly 'fields.giteaRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.githubRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.githubRepoPicker.owner.description': 'The organization, user or project that this repo will belong to'; + readonly 'fields.githubRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.gitlabRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.gitlabRepoPicker.owner.description': 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; + readonly 'fields.gitlabRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.repoUrlPickerHost.host.title': 'Host'; + readonly 'fields.repoUrlPickerHost.host.description': 'The host where the repository will be created'; + readonly 'fields.repoUrlPickerRepoName.repository.title': 'Repositories Available'; + readonly 'fields.repoUrlPickerRepoName.repository.description': 'The name of the repository'; + readonly 'fields.repoUrlPickerRepoName.repository.inputTitle': 'Repository'; + readonly 'actionsPage.content.emptyState.title': 'No information to display'; + readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; + readonly 'actionsPage.content.tableCell.type': 'Type'; + readonly 'actionsPage.content.tableCell.name': 'Name'; + readonly 'actionsPage.content.tableCell.title': 'Title'; + readonly 'actionsPage.content.tableCell.description': 'Description'; + readonly 'actionsPage.content.noRowsDescription': 'No schema defined'; + readonly 'actionsPage.title': 'Installed actions'; + readonly 'actionsPage.action.input': 'Input'; + readonly 'actionsPage.action.output': 'Output'; + readonly 'actionsPage.action.examples': 'Examples'; + readonly 'actionsPage.subtitle': 'This is the collection of all installed actions'; + readonly 'actionsPage.pageTitle': 'Create a New Component'; + readonly 'listTaskPage.content.emptyState.title': 'No information to display'; + readonly 'listTaskPage.content.emptyState.description': 'There are no tasks or there was an issue communicating with backend.'; + readonly 'listTaskPage.content.tableCell.template': 'Template'; + readonly 'listTaskPage.content.tableCell.status': 'Status'; + readonly 'listTaskPage.content.tableCell.owner': 'Owner'; + readonly 'listTaskPage.content.tableCell.created': 'Created'; + readonly 'listTaskPage.content.tableCell.taskID': 'Task ID'; + readonly 'listTaskPage.content.tableTitle': 'Tasks'; + readonly 'listTaskPage.title': 'List template tasks'; + readonly 'listTaskPage.subtitle': 'All tasks that have been started'; + readonly 'listTaskPage.pageTitle': 'Templates Tasks'; + readonly 'ownerListPicker.title': 'Task Owner'; + readonly 'ownerListPicker.options.all': 'All'; + readonly 'ownerListPicker.options.owned': 'Owned'; + readonly 'ongoingTask.title': 'Run of'; + readonly 'ongoingTask.contextMenu.cancel': 'Cancel'; + readonly 'ongoingTask.contextMenu.startOver': 'Start Over'; + readonly 'ongoingTask.contextMenu.hideLogs': 'Hide Logs'; + readonly 'ongoingTask.contextMenu.showLogs': 'Show Logs'; + readonly 'ongoingTask.contextMenu.hideButtonBar': 'Hide Button Bar'; + readonly 'ongoingTask.contextMenu.showButtonBar': 'Show Button Bar'; + readonly 'ongoingTask.subtitle': 'Task {{taskId}}'; + readonly 'ongoingTask.pageTitle.hasTemplateName': 'Run of {{templateName}}'; + readonly 'ongoingTask.pageTitle.noTemplateName': 'Scaffolder Run'; + readonly 'ongoingTask.cancelButtonTitle': 'Cancel'; + readonly 'ongoingTask.startOverButtonTitle': 'Start Over'; + readonly 'ongoingTask.hideLogsButtonTitle': 'Hide Logs'; + readonly 'ongoingTask.showLogsButtonTitle': 'Show Logs'; + readonly 'templateTypePicker.title': 'Categories'; + readonly 'templateEditorPage.title': 'Template Editor'; + readonly 'templateEditorPage.subTitle': 'Edit, preview, and try out templates and template forms'; + readonly 'templateEditorPage.dryRunResults.title': 'Dry-run results'; + readonly 'templateEditorPage.dryRunResultsList.title': 'Result {{resultId}}'; + readonly 'templateEditorPage.dryRunResultsList.deleteButtonTitle': 'Delete result'; + readonly 'templateEditorPage.dryRunResultsList.downloadButtonTitle': 'Download as .zip'; + readonly 'templateEditorPage.dryRunResultsView.tab.output': 'Output'; + readonly 'templateEditorPage.dryRunResultsView.tab.log': 'Log'; + readonly 'templateEditorPage.dryRunResultsView.tab.files': 'Files'; + readonly 'templateEditorPage.taskStatusStepper.skippedStepTitle': 'Skipped'; + readonly 'templateEditorPage.customFieldExplorer.selectFieldLabel': 'Choose Custom Field Extension'; + readonly 'templateEditorPage.customFieldExplorer.fieldForm.title': 'Field Options'; + readonly 'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle': 'Apply'; + readonly 'templateEditorPage.customFieldExplorer.preview.title': 'Example Template Spec'; + readonly 'templateEditorPage.templateEditorBrowser.closeConfirmMessage': 'Are you sure? Unsaved changes will be lost'; + readonly 'templateEditorPage.templateEditorBrowser.saveIconTooltip': 'Save all files'; + readonly 'templateEditorPage.templateEditorBrowser.reloadIconTooltip': 'Reload directory'; + readonly 'templateEditorPage.templateEditorBrowser.closeIconTooltip': 'Close directory'; + readonly 'templateEditorPage.templateEditorIntro.title': 'Get started by choosing one of the options below'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.title': 'Load Template Directory'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.description': 'Load a local template directory, allowing you to both edit and try executing your own template.'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.unSupportsLoadTooltip': 'Only supported in some Chromium-based browsers'; + readonly 'templateEditorPage.templateEditorIntro.formEditor.title': 'Edit Template Form'; + readonly 'templateEditorPage.templateEditorIntro.formEditor.description': 'Preview and edit a template form, either using a sample template or by loading a template from the catalog.'; + readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.title': 'Custom Field Explorer'; + readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.description': 'View and play around with available installed custom field extensions.'; + readonly 'templateEditorPage.templateEditorTextArea.saveIconTooltip': 'Save file'; + readonly 'templateEditorPage.templateEditorTextArea.refreshIconTooltip': 'Reload file'; + readonly 'templateEditorPage.templateFormPreviewer.title': 'Load Existing Template'; + readonly 'templateListPage.title': 'Create a new component'; + readonly 'templateListPage.subtitle': 'Create new software components using standard templates in your organization'; + readonly 'templateListPage.pageTitle': 'Create a new component'; + readonly 'templateListPage.templateGroups.defaultTitle': 'Templates'; + readonly 'templateListPage.templateGroups.otherTitle': 'Other Templates'; + readonly 'templateListPage.contentHeader.title': 'Available Templates'; + readonly 'templateListPage.contentHeader.registerExistingButtonTitle': 'Register Existing Component'; + readonly 'templateListPage.contentHeader.supportButtonTitle': 'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).'; + readonly 'templateListPage.additionalLinksForEntity.viewTechDocsTitle': 'View TechDocs'; + readonly 'templateWizardPage.title': 'Create a new component'; + readonly 'templateWizardPage.subtitle': 'Create new software components using standard templates in your organization'; + readonly 'templateWizardPage.pageTitle': 'Create a new component'; + readonly 'templateWizardPage.pageContextMenu.editConfigurationTitle': 'Edit Configuration'; + } +>; + // @alpha (undocumented) export type TemplateListPageProps = { TemplateCardComponent?: React_2.ComponentType<{ From fd775fc4a1b6f0909e7d9a8ee8f5bc5906f47a1d Mon Sep 17 00:00:00 2001 From: mario ma Date: Thu, 15 Aug 2024 14:39:54 +0800 Subject: [PATCH 368/393] fix: change some key Signed-off-by: mario ma --- plugins/scaffolder/api-report-alpha.md | 4 ++-- .../src/next/TemplateEditorPage/TemplateEditorIntro.tsx | 2 +- .../src/next/TemplateEditorPage/TemplateEditorPage.tsx | 2 +- plugins/scaffolder/src/translation.ts | 5 ++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index 2316f9c80c..f0a68919a4 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -198,7 +198,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'ongoingTask.showLogsButtonTitle': 'Show Logs'; readonly 'templateTypePicker.title': 'Categories'; readonly 'templateEditorPage.title': 'Template Editor'; - readonly 'templateEditorPage.subTitle': 'Edit, preview, and try out templates and template forms'; + readonly 'templateEditorPage.subtitle': 'Edit, preview, and try out templates and template forms'; readonly 'templateEditorPage.dryRunResults.title': 'Dry-run results'; readonly 'templateEditorPage.dryRunResultsList.title': 'Result {{resultId}}'; readonly 'templateEditorPage.dryRunResultsList.deleteButtonTitle': 'Delete result'; @@ -218,7 +218,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'templateEditorPage.templateEditorIntro.title': 'Get started by choosing one of the options below'; readonly 'templateEditorPage.templateEditorIntro.loadLocal.title': 'Load Template Directory'; readonly 'templateEditorPage.templateEditorIntro.loadLocal.description': 'Load a local template directory, allowing you to both edit and try executing your own template.'; - readonly 'templateEditorPage.templateEditorIntro.loadLocal.unSupportsLoadTooltip': 'Only supported in some Chromium-based browsers'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.unsupportedTooltip': 'Only supported in some Chromium-based browsers'; readonly 'templateEditorPage.templateEditorIntro.formEditor.title': 'Edit Template Form'; readonly 'templateEditorPage.templateEditorIntro.formEditor.description': 'Preview and edit a template form, either using a sample template or by loading a template from the catalog.'; readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.title': 'Custom Field Explorer'; diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx index 2318c90659..d785708a51 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx @@ -83,7 +83,7 @@ export function TemplateEditorIntro(props: EditorIntroProps) { diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx index 924fe6964e..6e7c921e91 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx @@ -121,7 +121,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) {
    diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts index eb6e4d005a..bf584bb7ad 100644 --- a/plugins/scaffolder/src/translation.ts +++ b/plugins/scaffolder/src/translation.ts @@ -191,7 +191,7 @@ export const scaffolderTranslationRef = createTranslationRef({ }, templateEditorPage: { title: 'Template Editor', - subTitle: 'Edit, preview, and try out templates and template forms', + subtitle: 'Edit, preview, and try out templates and template forms', dryRunResults: { title: 'Dry-run results', }, @@ -232,8 +232,7 @@ export const scaffolderTranslationRef = createTranslationRef({ title: 'Load Template Directory', description: 'Load a local template directory, allowing you to both edit and try executing your own template.', - unSupportsLoadTooltip: - 'Only supported in some Chromium-based browsers', + unsupportedTooltip: 'Only supported in some Chromium-based browsers', }, formEditor: { title: 'Edit Template Form', From c7a3611cf3f91a33a428943cbc6da08aa607fbfc Mon Sep 17 00:00:00 2001 From: mario ma Date: Thu, 15 Aug 2024 15:00:23 +0800 Subject: [PATCH 369/393] fix: alpha.tsx prettier Signed-off-by: mario ma --- plugins/scaffolder/src/alpha.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/alpha.tsx b/plugins/scaffolder/src/alpha.tsx index 5b7353053e..302dddb800 100644 --- a/plugins/scaffolder/src/alpha.tsx +++ b/plugins/scaffolder/src/alpha.tsx @@ -51,6 +51,8 @@ export { type TemplateWizardPageProps, } from './next'; +export { scaffolderTranslationRef } from './translation'; + const scaffolderApi = ApiBlueprint.make({ params: { factory: createApiFactory({ @@ -106,5 +108,3 @@ export default createFrontendPlugin({ }), extensions: [scaffolderApi, scaffolderPage, scaffolderNavItem], }); - -export * from './translation'; \ No newline at end of file From 52408cbbdfbfc2217567a9ffeb180e5ae9b9439b Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 20 Aug 2024 09:52:31 +0200 Subject: [PATCH 370/393] chore: updating some of the repo picker Signed-off-by: blam --- plugins/scaffolder/api-report-alpha.md | 10 +++++----- .../fields/RepoUrlPicker/GerritRepoPicker.tsx | 1 - .../fields/RepoUrlPicker/RepoUrlPickerHost.tsx | 4 ++-- .../fields/RepoUrlPicker/RepoUrlPickerRepoName.tsx | 6 +++--- plugins/scaffolder/src/translation.ts | 4 +--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index f0a68919a4..007b106665 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -150,11 +150,11 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'fields.gitlabRepoPicker.owner.title': 'Owner Available'; readonly 'fields.gitlabRepoPicker.owner.description': 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; readonly 'fields.gitlabRepoPicker.owner.inputTitle': 'Owner'; - readonly 'fields.repoUrlPickerHost.host.title': 'Host'; - readonly 'fields.repoUrlPickerHost.host.description': 'The host where the repository will be created'; - readonly 'fields.repoUrlPickerRepoName.repository.title': 'Repositories Available'; - readonly 'fields.repoUrlPickerRepoName.repository.description': 'The name of the repository'; - readonly 'fields.repoUrlPickerRepoName.repository.inputTitle': 'Repository'; + readonly 'fields.repoUrlPicker.host.title': 'Host'; + readonly 'fields.repoUrlPicker.host.description': 'The host where the repository will be created'; + readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; + readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; + readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; readonly 'actionsPage.content.emptyState.title': 'No information to display'; readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; readonly 'actionsPage.content.tableCell.type': 'Type'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx index 9a81fd3c48..bcc665976f 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx @@ -15,7 +15,6 @@ */ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; -import FormHelperText from '@material-ui/core/FormHelperText'; import TextField from '@material-ui/core/TextField'; import { BaseRepoUrlPickerProps } from './types'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index 86adc5ee20..90f8eaf449 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -76,7 +76,7 @@ export const RepoUrlPickerHost = (props: { onChange(String(Array.isArray(selected) ? selected[0] : selected)) } @@ -74,7 +74,7 @@ export const RepoUrlPickerRepoName = (props: { renderInput={params => ( )} @@ -83,7 +83,7 @@ export const RepoUrlPickerRepoName = (props: { /> )} - {t('fields.repoUrlPickerRepoName.repository.description')} + {t('fields.repoUrlPicker.repository.description')} diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts index bf584bb7ad..d97c3f6f05 100644 --- a/plugins/scaffolder/src/translation.ts +++ b/plugins/scaffolder/src/translation.ts @@ -125,13 +125,11 @@ export const scaffolderTranslationRef = createTranslationRef({ 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.', }, }, - repoUrlPickerHost: { + repoUrlPicker: { host: { title: 'Host', description: 'The host where the repository will be created', }, - }, - repoUrlPickerRepoName: { repository: { title: 'Repositories Available', inputTitle: 'Repository', From 006b7e66c867d3219fce3623f56fcecf4f1cb26e Mon Sep 17 00:00:00 2001 From: Simone Fumagalli Date: Tue, 20 Aug 2024 09:58:35 +0200 Subject: [PATCH 371/393] Removed TUI Musement TUI Musement is a part of TUI Group that is already listed. Signed-off-by: Simone Fumagalli --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 3b534052f1..abab08871d 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -134,7 +134,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [SafetyCulture](https://safetyculture.com/) | [@R-cen](https://github.com/R-cen), [@lachlancooper](https://github.com/lachlancooper), [@hkf57](https://github.com/hkf57) | Internal developer portal to provide a centralized place for engineers to see an overview of their team's services and information related to the service from other systems. Initially focused on the software catalog, techdocs and search. | | [Sana Life Science](https://sanalifescience.com) | [Joe Hillyard](mailto:joe@sanalifescience.com) | API Catalog, Tools Management & Control Hub | | [Ndustrial](https://ndustrial.io) | [Jonathan Skubic](mailto:jonathan@ndustrial.io) | Software Project Catalog | -| [TUI Musement](https://www.musement.com/uk/) | [Simone Fumagalli](mailto:simone.fumagalli@musement.com) | We are importing our catalog into it to keep it under control. The next step is start using templates | + | [Kambi AB](https://www.kambi.com) | [Martin Norum](mailto:martin.norum@kambi.com) | We want to kick ass at speed, so we're currently building up a catalog of our existing software, and looking into how Backstage can support us in our journey towards autonomous product teams. Both to improve speed to market and operational awareness. | | [ANZ](https://www.anz.com.au/personal/) | [Elliot Jackson](mailto:elliot.jackson@anz.com) | Catalog, tech docs and automation | | [Genie Solutions](https://www.geniesolutionssoftware.com.au) | [Zainab Bagasrawala](mailto:zainabbagasrawala@geniesolutions.com.au) | Developer Portal to track our projects, documentation, observability tools and more | From 2a2c84ba687d40f4b002be57828ed49331555f82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Aug 2024 08:56:34 +0000 Subject: [PATCH 372/393] Version Packages --- .changeset/angry-dolphins-camp.md | 5 - .changeset/angry-mice-juggle.md | 5 - .changeset/big-countries-wait.md | 5 - .changeset/big-eagles-grab.md | 5 - .changeset/blue-pumas-cheer.md | 5 - .changeset/breezy-cats-kiss.md | 5 - .changeset/breezy-jeans-tie.md | 18 - .changeset/breezy-rings-fly.md | 5 - .changeset/bright-donkeys-buy.md | 5 - .changeset/bright-trainers-brake.md | 5 - .changeset/brown-mails-drum.md | 5 - .changeset/calm-crabs-drop.md | 5 - .changeset/chilly-days-peel.md | 5 - .changeset/chilly-numbers-chew.md | 5 - .changeset/chilly-trains-sleep.md | 7 - .changeset/clean-kids-applaud.md | 5 - .changeset/clever-laws-raise.md | 6 - .changeset/clever-pans-brake.md | 5 - .changeset/cool-insects-remember.md | 5 - .changeset/cool-schools-vanish.md | 5 - .changeset/create-app-1722413762.md | 5 - .changeset/cuddly-penguins-glow.md | 7 - .changeset/cuddly-zebras-crash.md | 5 - .changeset/curly-cars-relax.md | 7 - .changeset/curly-clouds-shave.md | 5 - .changeset/curvy-pillows-joke.md | 5 - .changeset/cyan-shrimps-push.md | 5 - .changeset/dry-hotels-film.md | 12 - .changeset/dry-squids-tap.md | 27 - .changeset/dull-cheetahs-clap.md | 16 - .changeset/dull-ghosts-double.md | 7 - .changeset/early-trees-dance.md | 5 - .changeset/eight-days-sin.md | 5 - .changeset/eight-moose-pull.md | 5 - .changeset/eighty-emus-leave.md | 7 - .changeset/eighty-jokes-deny.md | 6 - .changeset/eighty-mirrors-flow.md | 5 - .changeset/eleven-dolphins-divide.md | 24 - .changeset/fair-hairs-mix.md | 6 - .changeset/fair-kangaroos-return.md | 15 - .changeset/fair-pumas-hang.md | 43 - .changeset/famous-cougars-walk.md | 5 - .changeset/fast-bulldogs-relax.md | 5 - .changeset/few-wasps-hug.md | 5 - .changeset/five-mangos-agree.md | 5 - .changeset/flat-bugs-drive.md | 5 - .changeset/flat-guests-behave.md | 6 - .changeset/flat-papayas-push.md | 5 - .changeset/flat-plums-grow.md | 5 - .changeset/forty-geckos-change.md | 5 - .changeset/forty-ties-agree.md | 18 - .changeset/forty-ties-disagree.md | 5 - .changeset/four-singers-fetch.md | 10 - .changeset/fresh-timers-walk.md | 20 - .changeset/friendly-cherries-applaud.md | 9 - .changeset/friendly-chicken-cry.md | 8 - .changeset/friendly-feet-refuse.md | 5 - .changeset/friendly-points-approve.md | 6 - .changeset/funny-bears-sort.md | 5 - .changeset/funny-tips-push.md | 5 - .changeset/funny-wolves-learn.md | 5 - .changeset/gentle-dryers-smile.md | 5 - .changeset/gentle-radios-cheat.md | 30 - .changeset/giant-buttons-guess.md | 14 - .changeset/good-balloons-fix.md | 5 - .changeset/good-steaks-report.md | 5 - .changeset/green-planets-reflect.md | 6 - .changeset/grumpy-owls-suffer.md | 5 - .changeset/healthy-carpets-reflect.md | 5 - .changeset/healthy-fireants-jump.md | 5 - .changeset/healthy-islands-smash.md | 5 - .changeset/healthy-timers-divide.md | 9 - .changeset/heavy-numbers-love.md | 5 - .changeset/hip-fishes-guess.md | 6 - .changeset/hip-hairs-exist.md | 6 - .changeset/itchy-experts-tie.md | 5 - .changeset/khaki-lamps-peel.md | 26 - .changeset/large-monkeys-dress.md | 5 - .changeset/late-games-protect.md | 5 - .changeset/late-timers-sort.md | 12 - .changeset/light-pianos-exercise.md | 5 - .changeset/little-bulldogs-guess.md | 5 - .changeset/little-suns-fly.md | 5 - .changeset/lovely-planes-shop.md | 5 - .changeset/lovely-ravens-judge.md | 36 - .changeset/many-years-lie.md | 5 - .changeset/mean-apricots-perform.md | 5 - .changeset/metal-planes-nail.md | 5 - .changeset/metal-rice-call.md | 5 - .changeset/mighty-apricots-taste.md | 5 - .changeset/mighty-dolls-retire.md | 5 - .changeset/mighty-geckos-kiss.md | 5 - .changeset/modern-apes-join.md | 5 - .changeset/modern-brooms-grow.md | 10 - .changeset/modern-parrots-protect.md | 5 - .changeset/modern-poems-mate.md | 5 - .changeset/neat-bears-divide.md | 5 - .changeset/neat-gifts-join.md | 5 - .changeset/neat-socks-cheer.md | 5 - .changeset/new-knives-fly.md | 5 - .changeset/new-scissors-try.md | 5 - .changeset/nice-peas-retire.md | 6 - .changeset/nine-cherries-decide.md | 14 - .changeset/nine-glasses-nail.md | 5 - .changeset/nine-seahorses-relate.md | 5 - .changeset/ninety-icons-smile.md | 5 - .changeset/odd-books-share.md | 5 - .changeset/old-tools-smell.md | 5 - .changeset/olive-books-sort.md | 5 - .changeset/orange-gifts-protect.md | 5 - .changeset/perfect-cars-jam.md | 5 - .changeset/pink-bobcats-explain.md | 5 - .changeset/pink-gorillas-brake.md | 5 - .changeset/plenty-mails-do.md | 5 - .changeset/plenty-tools-exist.md | 5 - .changeset/popular-panthers-hear.md | 5 - .changeset/pre.json | 344 -- .changeset/purple-carrots-crash.md | 5 - .changeset/quick-roses-juggle.md | 5 - .changeset/rare-foxes-compete.md | 57 - .changeset/rare-maps-wave.md | 6 - .changeset/real-lizards-sit.md | 5 - .changeset/red-radios-promise.md | 5 - .changeset/renovate-147ac48.md | 5 - .changeset/renovate-f04beb1.md | 5 - .changeset/rich-mugs-dress.md | 7 - .changeset/selfish-bees-think.md | 5 - .changeset/seven-days-film.md | 5 - .changeset/seven-eggs-admire.md | 5 - .changeset/shaggy-dodos-applaud.md | 5 - .changeset/shaggy-mugs-return.md | 5 - .changeset/shy-games-poke.md | 5 - .changeset/shy-waves-share.md | 5 - .changeset/silent-camels-walk.md | 5 - .changeset/silly-candles-sin.md | 5 - .changeset/silly-cycles-tan.md | 6 - .changeset/silly-scissors-turn.md | 5 - .changeset/silver-pillows-begin.md | 5 - .changeset/six-mails-smell.md | 5 - .changeset/six-rats-kick.md | 5 - .changeset/sixty-kiwis-poke.md | 5 - .changeset/slow-ducks-rush.md | 5 - .changeset/slow-ligers-drum.md | 5 - .changeset/slow-rocks-end.md | 5 - .changeset/slow-toes-jog.md | 5 - .changeset/small-bottles-cough.md | 58 - .changeset/small-ears-poke.md | 7 - .changeset/small-spoons-shout.md | 13 - .changeset/smooth-countries-relate.md | 5 - .changeset/soft-files-greet.md | 16 - .changeset/soft-gorillas-refuse.md | 5 - .changeset/spicy-jars-hear.md | 5 - .changeset/spicy-lies-listen.md | 5 - .changeset/spicy-planets-provide.md | 46 - .changeset/spotty-planets-accept.md | 5 - .changeset/strange-papayas-beg.md | 5 - .changeset/strong-chicken-kiss.md | 5 - .changeset/strong-otters-compete.md | 5 - .changeset/stupid-dots-relate.md | 5 - .changeset/sweet-oranges-buy.md | 5 - .changeset/swift-dragons-know.md | 5 - .changeset/swift-kings-sparkle.md | 5 - .changeset/tall-snakes-fix.md | 5 - .changeset/tame-doors-think.md | 9 - .changeset/tasty-ads-rescue.md | 5 - .changeset/ten-penguins-roll.md | 5 - .changeset/thick-hotels-know.md | 5 - .changeset/thick-squids-drive.md | 34 - .changeset/thin-carrots-eat.md | 5 - .changeset/thirty-adults-grab.md | 6 - .changeset/thirty-balloons-end.md | 5 - .changeset/thirty-dogs-wave.md | 5 - .changeset/thirty-paws-hope.md | 5 - .changeset/three-kiwis-turn.md | 19 - .changeset/tidy-guests-battle.md | 5 - .changeset/tiny-dodos-prove-2.md | 5 - .changeset/tiny-dodos-prove.md | 9 - .changeset/tiny-oranges-pretend.md | 19 - .changeset/tough-goats-hang.md | 5 - .changeset/tough-lies-repair.md | 5 - .changeset/tricky-ducks-juggle.md | 5 - .changeset/tricky-rules-destroy.md | 5 - .changeset/twelve-peaches-develop.md | 5 - .changeset/two-crabs-call.md | 5 - .changeset/two-emus-work.md | 5 - .changeset/violet-jokes-wave.md | 5 - .changeset/warm-moles-appear.md | 8 - .changeset/warm-monkeys-marry.md | 5 - .changeset/weak-jobs-joke.md | 6 - .changeset/wicked-bobcats-teach.md | 5 - .changeset/wild-eggs-exist.md | 10 - .changeset/wise-spiders-walk.md | 5 - .changeset/witty-bears-behave.md | 5 - .changeset/witty-geese-battle.md | 5 - .changeset/witty-queens-run.md | 5 - .changeset/witty-timers-marry.md | 5 - .changeset/young-birds-push.md | 5 - .changeset/young-games-visit.md | 14 - .changeset/young-peaches-shake.md | 5 - docs/releases/v1.30.0-changelog.md | 3093 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 11 + packages/app-defaults/package.json | 2 +- packages/app-next-example-plugin/CHANGELOG.md | 8 + packages/app-next-example-plugin/package.json | 2 +- packages/app-next/CHANGELOG.md | 43 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 41 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 26 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 67 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 32 + packages/backend-defaults/package.json | 2 +- packages/backend-dev-utils/CHANGELOG.md | 6 + packages/backend-dev-utils/package.json | 2 +- .../CHANGELOG.md | 32 + .../package.json | 2 +- packages/backend-legacy/CHANGELOG.md | 42 + packages/backend-legacy/package.json | 2 +- packages/backend-openapi-utils/CHANGELOG.md | 8 + packages/backend-openapi-utils/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 114 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 27 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 29 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 36 + packages/backend/package.json | 2 +- packages/catalog-client/CHANGELOG.md | 8 + packages/catalog-client/package.json | 2 +- packages/catalog-model/CHANGELOG.md | 12 + packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 41 + packages/cli/package.json | 2 +- packages/config-loader/CHANGELOG.md | 19 + packages/config-loader/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 11 + packages/core-app-api/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 14 + packages/core-compat-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 13 + packages/core-components/package.json | 2 +- packages/create-app/CHANGELOG.md | 12 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 14 + packages/dev-utils/package.json | 2 +- packages/e2e-test/CHANGELOG.md | 9 + packages/e2e-test/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 24 + packages/frontend-app-api/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 184 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 69 + packages/frontend-test-utils/package.json | 2 +- packages/integration-react/CHANGELOG.md | 9 + packages/integration-react/package.json | 2 +- packages/integration/CHANGELOG.md | 13 + packages/integration/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 12 + packages/repo-tools/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 19 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 11 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 13 + packages/test-utils/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 20 + plugins/api-docs/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 16 + plugins/app-backend/package.json | 2 +- plugins/app-node/CHANGELOG.md | 8 + plugins/app-node/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 12 + plugins/app-visualizer/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 34 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 32 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 18 + plugins/auth-node/package.json | 2 +- plugins/auth-react/CHANGELOG.md | 10 + plugins/auth-react/package.json | 2 +- plugins/bitbucket-cloud-common/CHANGELOG.md | 8 + plugins/bitbucket-cloud-common/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 24 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 17 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 20 + .../package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 16 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 26 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 20 + .../package.json | 2 +- .../CHANGELOG.md | 20 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 17 + .../catalog-backend-module-ldap/package.json | 2 +- .../catalog-backend-module-logs/CHANGELOG.md | 9 + .../catalog-backend-module-logs/package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 34 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-common/CHANGELOG.md | 9 + plugins/catalog-common/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 17 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 19 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 16 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 28 + plugins/catalog-react/package.json | 2 +- .../CHANGELOG.md | 7 + .../package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 10 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 31 + plugins/catalog/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 10 + plugins/config-schema/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 19 + plugins/devtools-backend/package.json | 2 +- plugins/devtools-common/CHANGELOG.md | 8 + plugins/devtools-common/package.json | 2 +- plugins/devtools/CHANGELOG.md | 14 + plugins/devtools/package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../events-backend-module-azure/CHANGELOG.md | 8 + .../events-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-gerrit/CHANGELOG.md | 8 + .../events-backend-module-gerrit/package.json | 2 +- .../events-backend-module-github/CHANGELOG.md | 9 + .../events-backend-module-github/package.json | 2 +- .../events-backend-module-gitlab/CHANGELOG.md | 9 + .../events-backend-module-gitlab/package.json | 2 +- .../events-backend-test-utils/CHANGELOG.md | 7 + .../events-backend-test-utils/package.json | 2 +- plugins/events-backend/CHANGELOG.md | 10 + plugins/events-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 7 + plugins/events-node/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 10 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list-common/CHANGELOG.md | 7 + plugins/example-todo-list-common/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 8 + plugins/example-todo-list/package.json | 2 +- plugins/home-react/CHANGELOG.md | 8 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 20 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 24 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 12 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 9 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 12 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 13 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 16 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 63 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 21 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 13 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 19 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 11 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 14 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 14 + plugins/permission-backend/package.json | 2 +- plugins/permission-common/CHANGELOG.md | 12 + plugins/permission-common/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 18 + plugins/permission-node/package.json | 2 +- plugins/permission-react/CHANGELOG.md | 9 + plugins/permission-react/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 12 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 24 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 40 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 9 + plugins/scaffolder-common/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 10 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 15 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 25 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 34 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 21 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 12 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 22 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 19 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 18 + plugins/search-backend/package.json | 2 +- plugins/search-common/CHANGELOG.md | 9 + plugins/search-common/package.json | 2 +- plugins/search-react/CHANGELOG.md | 17 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 19 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 14 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 12 + plugins/signals-node/package.json | 2 +- plugins/signals/CHANGELOG.md | 11 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 15 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 22 + plugins/techdocs-backend/package.json | 2 +- plugins/techdocs-common/CHANGELOG.md | 6 + plugins/techdocs-common/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 19 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 11 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 30 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 14 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 19 + plugins/user-settings/package.json | 2 +- yarn.lock | 332 +- 538 files changed, 6372 insertions(+), 2322 deletions(-) delete mode 100644 .changeset/angry-dolphins-camp.md delete mode 100644 .changeset/angry-mice-juggle.md delete mode 100644 .changeset/big-countries-wait.md delete mode 100644 .changeset/big-eagles-grab.md delete mode 100644 .changeset/blue-pumas-cheer.md delete mode 100644 .changeset/breezy-cats-kiss.md delete mode 100644 .changeset/breezy-jeans-tie.md delete mode 100644 .changeset/breezy-rings-fly.md delete mode 100644 .changeset/bright-donkeys-buy.md delete mode 100644 .changeset/bright-trainers-brake.md delete mode 100644 .changeset/brown-mails-drum.md delete mode 100644 .changeset/calm-crabs-drop.md delete mode 100644 .changeset/chilly-days-peel.md delete mode 100644 .changeset/chilly-numbers-chew.md delete mode 100644 .changeset/chilly-trains-sleep.md delete mode 100644 .changeset/clean-kids-applaud.md delete mode 100644 .changeset/clever-laws-raise.md delete mode 100644 .changeset/clever-pans-brake.md delete mode 100644 .changeset/cool-insects-remember.md delete mode 100644 .changeset/cool-schools-vanish.md delete mode 100644 .changeset/create-app-1722413762.md delete mode 100644 .changeset/cuddly-penguins-glow.md delete mode 100644 .changeset/cuddly-zebras-crash.md delete mode 100644 .changeset/curly-cars-relax.md delete mode 100644 .changeset/curly-clouds-shave.md delete mode 100644 .changeset/curvy-pillows-joke.md delete mode 100644 .changeset/cyan-shrimps-push.md delete mode 100644 .changeset/dry-hotels-film.md delete mode 100644 .changeset/dry-squids-tap.md delete mode 100644 .changeset/dull-cheetahs-clap.md delete mode 100644 .changeset/dull-ghosts-double.md delete mode 100644 .changeset/early-trees-dance.md delete mode 100644 .changeset/eight-days-sin.md delete mode 100644 .changeset/eight-moose-pull.md delete mode 100644 .changeset/eighty-emus-leave.md delete mode 100644 .changeset/eighty-jokes-deny.md delete mode 100644 .changeset/eighty-mirrors-flow.md delete mode 100644 .changeset/eleven-dolphins-divide.md delete mode 100644 .changeset/fair-hairs-mix.md delete mode 100644 .changeset/fair-kangaroos-return.md delete mode 100644 .changeset/fair-pumas-hang.md delete mode 100644 .changeset/famous-cougars-walk.md delete mode 100644 .changeset/fast-bulldogs-relax.md delete mode 100644 .changeset/few-wasps-hug.md delete mode 100644 .changeset/five-mangos-agree.md delete mode 100644 .changeset/flat-bugs-drive.md delete mode 100644 .changeset/flat-guests-behave.md delete mode 100644 .changeset/flat-papayas-push.md delete mode 100644 .changeset/flat-plums-grow.md delete mode 100644 .changeset/forty-geckos-change.md delete mode 100644 .changeset/forty-ties-agree.md delete mode 100644 .changeset/forty-ties-disagree.md delete mode 100644 .changeset/four-singers-fetch.md delete mode 100644 .changeset/fresh-timers-walk.md delete mode 100644 .changeset/friendly-cherries-applaud.md delete mode 100644 .changeset/friendly-chicken-cry.md delete mode 100644 .changeset/friendly-feet-refuse.md delete mode 100644 .changeset/friendly-points-approve.md delete mode 100644 .changeset/funny-bears-sort.md delete mode 100644 .changeset/funny-tips-push.md delete mode 100644 .changeset/funny-wolves-learn.md delete mode 100644 .changeset/gentle-dryers-smile.md delete mode 100644 .changeset/gentle-radios-cheat.md delete mode 100644 .changeset/giant-buttons-guess.md delete mode 100644 .changeset/good-balloons-fix.md delete mode 100644 .changeset/good-steaks-report.md delete mode 100644 .changeset/green-planets-reflect.md delete mode 100644 .changeset/grumpy-owls-suffer.md delete mode 100644 .changeset/healthy-carpets-reflect.md delete mode 100644 .changeset/healthy-fireants-jump.md delete mode 100644 .changeset/healthy-islands-smash.md delete mode 100644 .changeset/healthy-timers-divide.md delete mode 100644 .changeset/heavy-numbers-love.md delete mode 100644 .changeset/hip-fishes-guess.md delete mode 100644 .changeset/hip-hairs-exist.md delete mode 100644 .changeset/itchy-experts-tie.md delete mode 100644 .changeset/khaki-lamps-peel.md delete mode 100644 .changeset/large-monkeys-dress.md delete mode 100644 .changeset/late-games-protect.md delete mode 100644 .changeset/late-timers-sort.md delete mode 100644 .changeset/light-pianos-exercise.md delete mode 100644 .changeset/little-bulldogs-guess.md delete mode 100644 .changeset/little-suns-fly.md delete mode 100644 .changeset/lovely-planes-shop.md delete mode 100644 .changeset/lovely-ravens-judge.md delete mode 100644 .changeset/many-years-lie.md delete mode 100644 .changeset/mean-apricots-perform.md delete mode 100644 .changeset/metal-planes-nail.md delete mode 100644 .changeset/metal-rice-call.md delete mode 100644 .changeset/mighty-apricots-taste.md delete mode 100644 .changeset/mighty-dolls-retire.md delete mode 100644 .changeset/mighty-geckos-kiss.md delete mode 100644 .changeset/modern-apes-join.md delete mode 100644 .changeset/modern-brooms-grow.md delete mode 100644 .changeset/modern-parrots-protect.md delete mode 100644 .changeset/modern-poems-mate.md delete mode 100644 .changeset/neat-bears-divide.md delete mode 100644 .changeset/neat-gifts-join.md delete mode 100644 .changeset/neat-socks-cheer.md delete mode 100644 .changeset/new-knives-fly.md delete mode 100644 .changeset/new-scissors-try.md delete mode 100644 .changeset/nice-peas-retire.md delete mode 100644 .changeset/nine-cherries-decide.md delete mode 100644 .changeset/nine-glasses-nail.md delete mode 100644 .changeset/nine-seahorses-relate.md delete mode 100644 .changeset/ninety-icons-smile.md delete mode 100644 .changeset/odd-books-share.md delete mode 100644 .changeset/old-tools-smell.md delete mode 100644 .changeset/olive-books-sort.md delete mode 100644 .changeset/orange-gifts-protect.md delete mode 100644 .changeset/perfect-cars-jam.md delete mode 100644 .changeset/pink-bobcats-explain.md delete mode 100644 .changeset/pink-gorillas-brake.md delete mode 100644 .changeset/plenty-mails-do.md delete mode 100644 .changeset/plenty-tools-exist.md delete mode 100644 .changeset/popular-panthers-hear.md delete mode 100644 .changeset/pre.json delete mode 100644 .changeset/purple-carrots-crash.md delete mode 100644 .changeset/quick-roses-juggle.md delete mode 100644 .changeset/rare-foxes-compete.md delete mode 100644 .changeset/rare-maps-wave.md delete mode 100644 .changeset/real-lizards-sit.md delete mode 100644 .changeset/red-radios-promise.md delete mode 100644 .changeset/renovate-147ac48.md delete mode 100644 .changeset/renovate-f04beb1.md delete mode 100644 .changeset/rich-mugs-dress.md delete mode 100644 .changeset/selfish-bees-think.md delete mode 100644 .changeset/seven-days-film.md delete mode 100644 .changeset/seven-eggs-admire.md delete mode 100644 .changeset/shaggy-dodos-applaud.md delete mode 100644 .changeset/shaggy-mugs-return.md delete mode 100644 .changeset/shy-games-poke.md delete mode 100644 .changeset/shy-waves-share.md delete mode 100644 .changeset/silent-camels-walk.md delete mode 100644 .changeset/silly-candles-sin.md delete mode 100644 .changeset/silly-cycles-tan.md delete mode 100644 .changeset/silly-scissors-turn.md delete mode 100644 .changeset/silver-pillows-begin.md delete mode 100644 .changeset/six-mails-smell.md delete mode 100644 .changeset/six-rats-kick.md delete mode 100644 .changeset/sixty-kiwis-poke.md delete mode 100644 .changeset/slow-ducks-rush.md delete mode 100644 .changeset/slow-ligers-drum.md delete mode 100644 .changeset/slow-rocks-end.md delete mode 100644 .changeset/slow-toes-jog.md delete mode 100644 .changeset/small-bottles-cough.md delete mode 100644 .changeset/small-ears-poke.md delete mode 100644 .changeset/small-spoons-shout.md delete mode 100644 .changeset/smooth-countries-relate.md delete mode 100644 .changeset/soft-files-greet.md delete mode 100644 .changeset/soft-gorillas-refuse.md delete mode 100644 .changeset/spicy-jars-hear.md delete mode 100644 .changeset/spicy-lies-listen.md delete mode 100644 .changeset/spicy-planets-provide.md delete mode 100644 .changeset/spotty-planets-accept.md delete mode 100644 .changeset/strange-papayas-beg.md delete mode 100644 .changeset/strong-chicken-kiss.md delete mode 100644 .changeset/strong-otters-compete.md delete mode 100644 .changeset/stupid-dots-relate.md delete mode 100644 .changeset/sweet-oranges-buy.md delete mode 100644 .changeset/swift-dragons-know.md delete mode 100644 .changeset/swift-kings-sparkle.md delete mode 100644 .changeset/tall-snakes-fix.md delete mode 100644 .changeset/tame-doors-think.md delete mode 100644 .changeset/tasty-ads-rescue.md delete mode 100644 .changeset/ten-penguins-roll.md delete mode 100644 .changeset/thick-hotels-know.md delete mode 100644 .changeset/thick-squids-drive.md delete mode 100644 .changeset/thin-carrots-eat.md delete mode 100644 .changeset/thirty-adults-grab.md delete mode 100644 .changeset/thirty-balloons-end.md delete mode 100644 .changeset/thirty-dogs-wave.md delete mode 100644 .changeset/thirty-paws-hope.md delete mode 100644 .changeset/three-kiwis-turn.md delete mode 100644 .changeset/tidy-guests-battle.md delete mode 100644 .changeset/tiny-dodos-prove-2.md delete mode 100644 .changeset/tiny-dodos-prove.md delete mode 100644 .changeset/tiny-oranges-pretend.md delete mode 100644 .changeset/tough-goats-hang.md delete mode 100644 .changeset/tough-lies-repair.md delete mode 100644 .changeset/tricky-ducks-juggle.md delete mode 100644 .changeset/tricky-rules-destroy.md delete mode 100644 .changeset/twelve-peaches-develop.md delete mode 100644 .changeset/two-crabs-call.md delete mode 100644 .changeset/two-emus-work.md delete mode 100644 .changeset/violet-jokes-wave.md delete mode 100644 .changeset/warm-moles-appear.md delete mode 100644 .changeset/warm-monkeys-marry.md delete mode 100644 .changeset/weak-jobs-joke.md delete mode 100644 .changeset/wicked-bobcats-teach.md delete mode 100644 .changeset/wild-eggs-exist.md delete mode 100644 .changeset/wise-spiders-walk.md delete mode 100644 .changeset/witty-bears-behave.md delete mode 100644 .changeset/witty-geese-battle.md delete mode 100644 .changeset/witty-queens-run.md delete mode 100644 .changeset/witty-timers-marry.md delete mode 100644 .changeset/young-birds-push.md delete mode 100644 .changeset/young-games-visit.md delete mode 100644 .changeset/young-peaches-shake.md create mode 100644 docs/releases/v1.30.0-changelog.md diff --git a/.changeset/angry-dolphins-camp.md b/.changeset/angry-dolphins-camp.md deleted file mode 100644 index 3c598f4a66..0000000000 --- a/.changeset/angry-dolphins-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Add access restrictions to the JWKS external access method config schema diff --git a/.changeset/angry-mice-juggle.md b/.changeset/angry-mice-juggle.md deleted file mode 100644 index c5c90b7365..0000000000 --- a/.changeset/angry-mice-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Export `RelatedEntitiesCard` presets to be reused. diff --git a/.changeset/big-countries-wait.md b/.changeset/big-countries-wait.md deleted file mode 100644 index 15c48b6d44..0000000000 --- a/.changeset/big-countries-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Add an extra bit of height to the EntityPicker dropdown to make it clear there are more options to select from, and to remove the scroll bar when there is less than 10 options diff --git a/.changeset/big-eagles-grab.md b/.changeset/big-eagles-grab.md deleted file mode 100644 index 9ece8f64df..0000000000 --- a/.changeset/big-eagles-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added examples for github:environment:create action and improve its test cases diff --git a/.changeset/blue-pumas-cheer.md b/.changeset/blue-pumas-cheer.md deleted file mode 100644 index f6cbf180ff..0000000000 --- a/.changeset/blue-pumas-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added the Kubernetes plugin to `create-app` diff --git a/.changeset/breezy-cats-kiss.md b/.changeset/breezy-cats-kiss.md deleted file mode 100644 index 900016c1e8..0000000000 --- a/.changeset/breezy-cats-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Fixing issue with extension blueprints `inputs` merging. diff --git a/.changeset/breezy-jeans-tie.md b/.changeset/breezy-jeans-tie.md deleted file mode 100644 index 4601389a4c..0000000000 --- a/.changeset/breezy-jeans-tie.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch -'@backstage/plugin-auth-backend-module-vmware-cloud-provider': patch -'@backstage/plugin-auth-backend-module-atlassian-provider': patch -'@backstage/plugin-auth-backend-module-bitbucket-provider': patch -'@backstage/plugin-auth-backend-module-microsoft-provider': patch -'@backstage/plugin-auth-backend-module-onelogin-provider': patch -'@backstage/plugin-auth-backend-module-aws-alb-provider': patch -'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch -'@backstage/plugin-auth-backend-module-github-provider': patch -'@backstage/plugin-auth-backend-module-gitlab-provider': patch -'@backstage/plugin-auth-backend-module-google-provider': patch -'@backstage/plugin-auth-backend-module-oauth2-provider': patch -'@backstage/plugin-auth-backend-module-oidc-provider': patch -'@backstage/plugin-auth-backend-module-okta-provider': patch ---- - -Add `signIn` to authentication provider configuration schema diff --git a/.changeset/breezy-rings-fly.md b/.changeset/breezy-rings-fly.md deleted file mode 100644 index c3cc1c143d..0000000000 --- a/.changeset/breezy-rings-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes diff --git a/.changeset/bright-donkeys-buy.md b/.changeset/bright-donkeys-buy.md deleted file mode 100644 index ded16ccc2c..0000000000 --- a/.changeset/bright-donkeys-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. diff --git a/.changeset/bright-trainers-brake.md b/.changeset/bright-trainers-brake.md deleted file mode 100644 index 2299d83a32..0000000000 --- a/.changeset/bright-trainers-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add frontend-dynamic-container role to eslint config factory diff --git a/.changeset/brown-mails-drum.md b/.changeset/brown-mails-drum.md deleted file mode 100644 index 97d57ff394..0000000000 --- a/.changeset/brown-mails-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': minor ---- - -**BREAKING**: Removed several deprecated service factories. These can instead be imported from `@backstage/backend-defaults` package. diff --git a/.changeset/calm-crabs-drop.md b/.changeset/calm-crabs-drop.md deleted file mode 100644 index 09bed7c091..0000000000 --- a/.changeset/calm-crabs-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. diff --git a/.changeset/chilly-days-peel.md b/.changeset/chilly-days-peel.md deleted file mode 100644 index d82344d364..0000000000 --- a/.changeset/chilly-days-peel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Added support for using the `params` in other properties of the `createExtensionBlueprint` options by providing a callback. diff --git a/.changeset/chilly-numbers-chew.md b/.changeset/chilly-numbers-chew.md deleted file mode 100644 index 3ee9e7837f..0000000000 --- a/.changeset/chilly-numbers-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Renamed `createPlugin` to `createFrontendPlugin`. The old symbol is still exported but deprecated. diff --git a/.changeset/chilly-trains-sleep.md b/.changeset/chilly-trains-sleep.md deleted file mode 100644 index cde7600c8b..0000000000 --- a/.changeset/chilly-trains-sleep.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-events-backend-module-aws-sqs': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/backend-common': patch ---- - -Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. diff --git a/.changeset/clean-kids-applaud.md b/.changeset/clean-kids-applaud.md deleted file mode 100644 index 907b5978cd..0000000000 --- a/.changeset/clean-kids-applaud.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Fixed issue with notification reloading on page change diff --git a/.changeset/clever-laws-raise.md b/.changeset/clever-laws-raise.md deleted file mode 100644 index 8733642cc8..0000000000 --- a/.changeset/clever-laws-raise.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-react': patch ---- - -Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. diff --git a/.changeset/clever-pans-brake.md b/.changeset/clever-pans-brake.md deleted file mode 100644 index 1f49c7a842..0000000000 --- a/.changeset/clever-pans-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Added new APIs for testing extensions diff --git a/.changeset/cool-insects-remember.md b/.changeset/cool-insects-remember.md deleted file mode 100644 index ab33015377..0000000000 --- a/.changeset/cool-insects-remember.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-notifications': patch ---- - -Add examples for notification:send scaffolder action & improve related tests diff --git a/.changeset/cool-schools-vanish.md b/.changeset/cool-schools-vanish.md deleted file mode 100644 index 9b2ded5248..0000000000 --- a/.changeset/cool-schools-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fixing loading of additional config files with new `ConfigSources` diff --git a/.changeset/create-app-1722413762.md b/.changeset/create-app-1722413762.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1722413762.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/cuddly-penguins-glow.md b/.changeset/cuddly-penguins-glow.md deleted file mode 100644 index b00422cf8b..0000000000 --- a/.changeset/cuddly-penguins-glow.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -`ProcessorOutputCollector` returns an error when receiving deferred entities that have an invalid `metadata.annotations` format. - -This allows to return an error on an actual validation issue instead of reporting that the location annotations are missing afterwards, which is misleading for the users. diff --git a/.changeset/cuddly-zebras-crash.md b/.changeset/cuddly-zebras-crash.md deleted file mode 100644 index 473ac4053f..0000000000 --- a/.changeset/cuddly-zebras-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Use ES2022 in CLI bundler diff --git a/.changeset/curly-cars-relax.md b/.changeset/curly-cars-relax.md deleted file mode 100644 index de1b4c3d89..0000000000 --- a/.changeset/curly-cars-relax.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-catalog': patch ---- - -Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. diff --git a/.changeset/curly-clouds-shave.md b/.changeset/curly-clouds-shave.md deleted file mode 100644 index 0e30f4bba1..0000000000 --- a/.changeset/curly-clouds-shave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Correct the `TConfig` type of data references to only contain config diff --git a/.changeset/curvy-pillows-joke.md b/.changeset/curvy-pillows-joke.md deleted file mode 100644 index 25966a51c0..0000000000 --- a/.changeset/curvy-pillows-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. diff --git a/.changeset/cyan-shrimps-push.md b/.changeset/cyan-shrimps-push.md deleted file mode 100644 index cb326b4da3..0000000000 --- a/.changeset/cyan-shrimps-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. diff --git a/.changeset/dry-hotels-film.md b/.changeset/dry-hotels-film.md deleted file mode 100644 index 66e2152eb2..0000000000 --- a/.changeset/dry-hotels-film.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-bitbucket-server': patch -'@backstage/plugin-catalog-backend-module-github-org': patch -'@backstage/plugin-catalog-backend-module-puppetdb': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-catalog-backend-module-gerrit': patch -'@backstage/plugin-catalog-backend-module-azure': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/plugin-catalog-backend-module-gcp': patch ---- - -Removed unused dependency diff --git a/.changeset/dry-squids-tap.md b/.changeset/dry-squids-tap.md deleted file mode 100644 index 0a95332eed..0000000000 --- a/.changeset/dry-squids-tap.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint` - -This allows the creation of extension instances with the following pattern: - -```tsx -// create the extension blueprint which is used to create instances -const EntityCardBlueprint = createExtensionBlueprint({ - kind: 'entity-card', - attachTo: { id: 'test', input: 'default' }, - output: [coreExtensionData.reactElement], - factory(params: { text: string }) { - return [coreExtensionData.reactElement(

    {params.text}

    )]; - }, -}); - -// create an instance of the extension blueprint with params -const testExtension = EntityCardBlueprint.make({ - name: 'foo', - params: { - text: 'Hello World', - }, -}); -``` diff --git a/.changeset/dull-cheetahs-clap.md b/.changeset/dull-cheetahs-clap.md deleted file mode 100644 index a2ea657f2e..0000000000 --- a/.changeset/dull-cheetahs-clap.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch -'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/backend-dynamic-feature-service': patch -'@backstage/plugin-scaffolder-backend-module-rails': patch -'@backstage/plugin-catalog-backend-module-openapi': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-scaffolder-node': patch -'@backstage/plugin-techdocs-node': patch ---- - -Update deprecated url-reader-related imports. diff --git a/.changeset/dull-ghosts-double.md b/.changeset/dull-ghosts-double.md deleted file mode 100644 index 0bc5a09791..0000000000 --- a/.changeset/dull-ghosts-double.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-home': patch ---- - -Updated alpha definitions of extension data references. diff --git a/.changeset/early-trees-dance.md b/.changeset/early-trees-dance.md deleted file mode 100644 index 55b2c08f89..0000000000 --- a/.changeset/early-trees-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path. diff --git a/.changeset/eight-days-sin.md b/.changeset/eight-days-sin.md deleted file mode 100644 index 71b87ee93d..0000000000 --- a/.changeset/eight-days-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Deprecated `inputs` and `configSchema` options for `createComponentExtenion`, these will be removed in a future release diff --git a/.changeset/eight-moose-pull.md b/.changeset/eight-moose-pull.md deleted file mode 100644 index c5bc5ee66e..0000000000 --- a/.changeset/eight-moose-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-react': patch ---- - -`Liveness Probe` added in ContainerCard Component of PodDrawer diff --git a/.changeset/eighty-emus-leave.md b/.changeset/eighty-emus-leave.md deleted file mode 100644 index 8e02c7cf94..0000000000 --- a/.changeset/eighty-emus-leave.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-techdocs-node': patch -'@backstage/plugin-techdocs': patch ---- - -Use annotation constants from new techdocs-common package. diff --git a/.changeset/eighty-jokes-deny.md b/.changeset/eighty-jokes-deny.md deleted file mode 100644 index 58944252f4..0000000000 --- a/.changeset/eighty-jokes-deny.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-scaffolder-node': patch ---- - -Add support for status filtering in scaffolder tasks endpoint diff --git a/.changeset/eighty-mirrors-flow.md b/.changeset/eighty-mirrors-flow.md deleted file mode 100644 index 57e978a78c..0000000000 --- a/.changeset/eighty-mirrors-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component diff --git a/.changeset/eleven-dolphins-divide.md b/.changeset/eleven-dolphins-divide.md deleted file mode 100644 index a35702323c..0000000000 --- a/.changeset/eleven-dolphins-divide.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-aws-alb-provider': patch ---- - -Added a `signer` configuration option to validate against the token claims. We strongly recommend that you set this value (typically on the format `arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456`) to ensure that the auth provider can safely check the authenticity of any incoming tokens. - -Example: - -```diff - auth: - providers: - awsalb: - # this is the URL of the IdP you configured - issuer: 'https://example.okta.com/oauth2/default' - # this is the ARN of your ALB instance -+ signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' - # this is the region where your ALB instance resides - region: 'us-west-2' - signIn: - resolvers: - # typically you would pick one of these - - resolver: emailMatchingUserEntityProfileEmail - - resolver: emailLocalPartMatchingUserEntityName -``` diff --git a/.changeset/fair-hairs-mix.md b/.changeset/fair-hairs-mix.md deleted file mode 100644 index 6c06c7bf72..0000000000 --- a/.changeset/fair-hairs-mix.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-permission-common': patch ---- - -Add the MetadataResponse type from @backstage/plugin-permission-node, since this -type might be used in frontend code. diff --git a/.changeset/fair-kangaroos-return.md b/.changeset/fair-kangaroos-return.md deleted file mode 100644 index 6c5b2e57ae..0000000000 --- a/.changeset/fair-kangaroos-return.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/backend-plugin-api': minor ---- - -**BREAKING** Deleted the following deprecated `UrlReader` exports - -- ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; -- ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; -- ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` instead; -- ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` instead; -- ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` instead; -- ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; -- SearchOptions: Use `UrlReaderServiceSearchOptions` instead; -- SearchResponse: Use `UrlReaderServiceSearchResponse` instead; -- SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` instead. diff --git a/.changeset/fair-pumas-hang.md b/.changeset/fair-pumas-hang.md deleted file mode 100644 index 156779b76a..0000000000 --- a/.changeset/fair-pumas-hang.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch -'@backstage/plugin-search-backend-module-stack-overflow-collator': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch -'@backstage/plugin-catalog-backend-module-bitbucket-server': patch -'@backstage/plugin-auth-backend-module-microsoft-provider': patch -'@backstage/plugin-auth-backend-module-aws-alb-provider': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket': patch -'@backstage/plugin-scaffolder-backend-module-gerrit': patch -'@backstage/plugin-scaffolder-backend-module-sentry': patch -'@backstage/plugin-catalog-backend-module-puppetdb': patch -'@backstage/plugin-scaffolder-backend-module-gitea': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-search-backend-module-techdocs': patch -'@backstage/plugin-catalog-backend-module-gerrit': patch -'@backstage/plugin-catalog-backend-module-github': patch -'@backstage/plugin-catalog-backend-module-gitlab': patch -'@backstage/plugin-search-backend-module-explore': patch -'@backstage/plugin-catalog-backend-module-azure': patch -'@backstage/plugin-notifications-backend': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-notifications-node': patch -'@backstage/plugin-permission-backend': patch -'@backstage/backend-defaults': patch -'@backstage/backend-app-api': patch -'@backstage/plugin-devtools-backend': patch -'@backstage/plugin-techdocs-backend': patch -'@backstage/backend-common': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-kubernetes-node': patch -'@backstage/plugin-signals-backend': patch -'@backstage/config-loader': patch -'@backstage/plugin-proxy-backend': patch -'@backstage/plugin-auth-backend': patch -'@backstage/create-app': patch -'@backstage/plugin-app-backend': patch -'@backstage/plugin-auth-node': patch -'@backstage/cli': patch ---- - -Make sure node-fetch is version 2.7.0 or greater diff --git a/.changeset/famous-cougars-walk.md b/.changeset/famous-cougars-walk.md deleted file mode 100644 index 465b03f86d..0000000000 --- a/.changeset/famous-cougars-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -This package is marked as `deprecated` and will be removed in a near future, please follow the deprecated instructions for the exports you still use. diff --git a/.changeset/fast-bulldogs-relax.md b/.changeset/fast-bulldogs-relax.md deleted file mode 100644 index 65895345e9..0000000000 --- a/.changeset/fast-bulldogs-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch ---- - -Added autocompletion support for resource `branches` diff --git a/.changeset/few-wasps-hug.md b/.changeset/few-wasps-hug.md deleted file mode 100644 index 66cf34fc0b..0000000000 --- a/.changeset/few-wasps-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Correct `EntityDisplayName`'s icon alignment with the text. diff --git a/.changeset/five-mangos-agree.md b/.changeset/five-mangos-agree.md deleted file mode 100644 index f5ecb2891b..0000000000 --- a/.changeset/five-mangos-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch ---- - -Add ability to set the initial commit message when initializing a repository using the scaffolder action. diff --git a/.changeset/flat-bugs-drive.md b/.changeset/flat-bugs-drive.md deleted file mode 100644 index dcdc65496f..0000000000 --- a/.changeset/flat-bugs-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -Deprecated `RouterOptions`, `CatalogBuilder`, and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. diff --git a/.changeset/flat-guests-behave.md b/.changeset/flat-guests-behave.md deleted file mode 100644 index ad4b238e03..0000000000 --- a/.changeset/flat-guests-behave.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch -'@backstage/plugin-notifications-backend': patch ---- - -Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. diff --git a/.changeset/flat-papayas-push.md b/.changeset/flat-papayas-push.md deleted file mode 100644 index f9e20613fd..0000000000 --- a/.changeset/flat-papayas-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-azure': patch ---- - -Added examples for publish:azure action and updated its test cases diff --git a/.changeset/flat-plums-grow.md b/.changeset/flat-plums-grow.md deleted file mode 100644 index 7057216786..0000000000 --- a/.changeset/flat-plums-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps` diff --git a/.changeset/forty-geckos-change.md b/.changeset/forty-geckos-change.md deleted file mode 100644 index 484a485670..0000000000 --- a/.changeset/forty-geckos-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -`ConsumingComponentsCard` and `ProvidingComponentsCard` will now optionally accept `columns` to override which table columns are displayed diff --git a/.changeset/forty-ties-agree.md b/.changeset/forty-ties-agree.md deleted file mode 100644 index caa45841a2..0000000000 --- a/.changeset/forty-ties-agree.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons. - -```tsx -import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; - -const exampleIconBundle = IconBundleBlueprint.make({ - name: 'example-bundle', - params: { - icons: { - user: MyOwnUserIcon, - }, - }, -}); -``` diff --git a/.changeset/forty-ties-disagree.md b/.changeset/forty-ties-disagree.md deleted file mode 100644 index 214097b35d..0000000000 --- a/.changeset/forty-ties-disagree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': patch ---- - -Support icon overriding with the new `IconBundleBlueprint` API. diff --git a/.changeset/four-singers-fetch.md b/.changeset/four-singers-fetch.md deleted file mode 100644 index 69fe6f77bf..0000000000 --- a/.changeset/four-singers-fetch.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/backend-test-utils': patch -'@backstage/plugin-kubernetes-node': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-proxy-backend': patch -'@backstage/backend-dynamic-feature-service': patch ---- - -Update internal imports diff --git a/.changeset/fresh-timers-walk.md b/.changeset/fresh-timers-walk.md deleted file mode 100644 index a187bede83..0000000000 --- a/.changeset/fresh-timers-walk.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Support overriding of plugin extensions using the new `plugin.withOverrides` method. - -```tsx -import homePlugin from '@backstage/plugin-home'; - -export default homePlugin.withOverrides({ - extensions: [ - homePage.getExtension('page:home').override({ - *factory(originalFactory) { - yield* originalFactory(); - yield coreExtensionData.reactElement(

    My custom home page

    ); - }, - }), - ], -}); -``` diff --git a/.changeset/friendly-cherries-applaud.md b/.changeset/friendly-cherries-applaud.md deleted file mode 100644 index c0160858f2..0000000000 --- a/.changeset/friendly-cherries-applaud.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-permission-node': patch ---- - -The MetadataResponse type has been moved to @backstage/plugin-permission-common -to match the recent move of MetadataResponseSerializedRule, and should be -imported from there going forward. To avoid an immediate breaking change, this -type is still re-exported from this package, but is marked as deprecated and -will be removed in a future release. diff --git a/.changeset/friendly-chicken-cry.md b/.changeset/friendly-chicken-cry.md deleted file mode 100644 index 3eadca8777..0000000000 --- a/.changeset/friendly-chicken-cry.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch -'@backstage/frontend-app-api': patch -'@backstage/core-compat-api': patch -'@backstage/plugin-app-visualizer': patch ---- - -Updated usage of `useRouteRef`, which can now always return `undefined`. diff --git a/.changeset/friendly-feet-refuse.md b/.changeset/friendly-feet-refuse.md deleted file mode 100644 index 677d21d48a..0000000000 --- a/.changeset/friendly-feet-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-rails': patch ---- - -Add examples for fetch:rails scaffolder action & improve related tests diff --git a/.changeset/friendly-points-approve.md b/.changeset/friendly-points-approve.md deleted file mode 100644 index 52de593750..0000000000 --- a/.changeset/friendly-points-approve.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Added deprecation warning to urge users to perform the auth service migration or implement their own token manager service. -See https://backstage.io/docs/tutorials/auth-service-migration for more information. diff --git a/.changeset/funny-bears-sort.md b/.changeset/funny-bears-sort.md deleted file mode 100644 index a3dad0f489..0000000000 --- a/.changeset/funny-bears-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched the `process` polyfill to use `require.resolve` for greater compatability. diff --git a/.changeset/funny-tips-push.md b/.changeset/funny-tips-push.md deleted file mode 100644 index 224e6a073a..0000000000 --- a/.changeset/funny-tips-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -update the `morgan` middleware to use a custom format to prevent PII from being logged diff --git a/.changeset/funny-wolves-learn.md b/.changeset/funny-wolves-learn.md deleted file mode 100644 index 2010ccd68b..0000000000 --- a/.changeset/funny-wolves-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:issues:create examples diff --git a/.changeset/gentle-dryers-smile.md b/.changeset/gentle-dryers-smile.md deleted file mode 100644 index b061461da1..0000000000 --- a/.changeset/gentle-dryers-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. diff --git a/.changeset/gentle-radios-cheat.md b/.changeset/gentle-radios-cheat.md deleted file mode 100644 index 9fcaef11d3..0000000000 --- a/.changeset/gentle-radios-cheat.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@backstage/frontend-app-api': minor -'@backstage/backend-app-api': minor -'@backstage/backend-dynamic-feature-service': minor -'@backstage/plugin-catalog-backend-module-aws': minor -'@backstage/plugin-catalog-backend-module-azure': minor -'@backstage/plugin-catalog-backend-module-backstage-openapi': minor -'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor -'@backstage/plugin-catalog-backend-module-bitbucket-server': minor -'@backstage/plugin-catalog-backend-module-gcp': minor -'@backstage/plugin-catalog-backend-module-gerrit': minor -'@backstage/plugin-catalog-backend-module-github-org': minor -'@backstage/plugin-catalog-backend-module-github': minor -'@backstage/plugin-catalog-backend-module-gitlab-org': minor -'@backstage/plugin-catalog-backend-module-gitlab': minor -'@backstage/plugin-catalog-backend-module-incremental-ingestion': minor -'@backstage/plugin-catalog-backend-module-ldap': minor -'@backstage/plugin-catalog-backend-module-msgraph': minor -'@backstage/plugin-catalog-backend-module-puppetdb': minor -'@backstage/plugin-catalog-backend': minor -'@backstage/plugin-events-backend-module-aws-sqs': minor -'@backstage/plugin-scaffolder-backend': minor -'@backstage/plugin-search-backend-module-catalog': minor -'@backstage/plugin-search-backend-module-explore': minor -'@backstage/plugin-search-backend-module-stack-overflow-collator': minor -'@backstage/plugin-search-backend-module-techdocs': minor -'@backstage/plugin-search-backend-node': minor ---- - -Stop using `@backstage/backend-tasks` as it will be deleted in near future. diff --git a/.changeset/giant-buttons-guess.md b/.changeset/giant-buttons-guess.md deleted file mode 100644 index 7a7842259e..0000000000 --- a/.changeset/giant-buttons-guess.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/plugin-app-visualizer': patch -'@backstage/plugin-catalog-graph': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-devtools': patch -'@backstage/plugin-org': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-search': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-user-settings': patch ---- - -Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead diff --git a/.changeset/good-balloons-fix.md b/.changeset/good-balloons-fix.md deleted file mode 100644 index be3d8e5785..0000000000 --- a/.changeset/good-balloons-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Add an `ExtensionBoundary.lazy` function to create properly wrapped lazy-loading enabled elements, suitable for use with `coreExtensionData.reactElement`. The page blueprint now automatically leverages this. diff --git a/.changeset/good-steaks-report.md b/.changeset/good-steaks-report.md deleted file mode 100644 index 5f9ce25470..0000000000 --- a/.changeset/good-steaks-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitea': patch ---- - -Added test cases for publish:gitea examples diff --git a/.changeset/green-planets-reflect.md b/.changeset/green-planets-reflect.md deleted file mode 100644 index 513f0480a5..0000000000 --- a/.changeset/green-planets-reflect.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch -'@backstage/frontend-app-api': patch ---- - -Added support for v2 extensions, which declare their inputs and outputs without using a data map. diff --git a/.changeset/grumpy-owls-suffer.md b/.changeset/grumpy-owls-suffer.md deleted file mode 100644 index 977055eb33..0000000000 --- a/.changeset/grumpy-owls-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Allow the `createGitlabProjectVariableAction` to use oauth tokens diff --git a/.changeset/healthy-carpets-reflect.md b/.changeset/healthy-carpets-reflect.md deleted file mode 100644 index ac7734a448..0000000000 --- a/.changeset/healthy-carpets-reflect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': minor ---- - -Remove deprecated `urlReaderServiceFactory`, please import from `@backstage/backend-defaults/urlReader` instead. diff --git a/.changeset/healthy-fireants-jump.md b/.changeset/healthy-fireants-jump.md deleted file mode 100644 index 7a11015b80..0000000000 --- a/.changeset/healthy-fireants-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. diff --git a/.changeset/healthy-islands-smash.md b/.changeset/healthy-islands-smash.md deleted file mode 100644 index 782a88167c..0000000000 --- a/.changeset/healthy-islands-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Broadcast notifications are now decorated with an icon. diff --git a/.changeset/healthy-timers-divide.md b/.changeset/healthy-timers-divide.md deleted file mode 100644 index 0b9d700723..0000000000 --- a/.changeset/healthy-timers-divide.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-search-backend': patch -'@backstage/plugin-search-common': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-search': patch ---- - -Fix package metadata diff --git a/.changeset/heavy-numbers-love.md b/.changeset/heavy-numbers-love.md deleted file mode 100644 index c18d144636..0000000000 --- a/.changeset/heavy-numbers-love.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. diff --git a/.changeset/hip-fishes-guess.md b/.changeset/hip-fishes-guess.md deleted file mode 100644 index 909448c673..0000000000 --- a/.changeset/hip-fishes-guess.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch -'@backstage/backend-defaults': patch ---- - -The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. diff --git a/.changeset/hip-hairs-exist.md b/.changeset/hip-hairs-exist.md deleted file mode 100644 index d8cb939dfb..0000000000 --- a/.changeset/hip-hairs-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-node': patch ---- - -Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint diff --git a/.changeset/itchy-experts-tie.md b/.changeset/itchy-experts-tie.md deleted file mode 100644 index c5bf99ecaf..0000000000 --- a/.changeset/itchy-experts-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added examples for github:repo:create action and improved test cases diff --git a/.changeset/khaki-lamps-peel.md b/.changeset/khaki-lamps-peel.md deleted file mode 100644 index 82a2cbc9de..0000000000 --- a/.changeset/khaki-lamps-peel.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. - -A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. - -Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. - -```ts -const searchLoader = createBackendFeatureLoader({ - deps: { - config: coreServices.rootConfig, - }, - *loader({ config }) { - // Example of a custom config flag to enable search - if (config.getOptionalString('customFeatureToggle.search')) { - yield import('@backstage/plugin-search-backend/alpha'); - yield import('@backstage/plugin-search-backend-module-catalog/alpha'); - yield import('@backstage/plugin-search-backend-module-explore/alpha'); - yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); - } - }, -}); -``` diff --git a/.changeset/large-monkeys-dress.md b/.changeset/large-monkeys-dress.md deleted file mode 100644 index 369a3994c5..0000000000 --- a/.changeset/large-monkeys-dress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Added OpenTelemetry support to Scaffolder metrics diff --git a/.changeset/late-games-protect.md b/.changeset/late-games-protect.md deleted file mode 100644 index 584780f836..0000000000 --- a/.changeset/late-games-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': minor ---- - -Add scaffolder option to display object items in separate rows on review page diff --git a/.changeset/late-timers-sort.md b/.changeset/late-timers-sort.md deleted file mode 100644 index d6f5544973..0000000000 --- a/.changeset/late-timers-sort.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/backend-test-utils': minor ---- - -**BREAKING**: Removed these deprecated helpers: - -- `setupRequestMockHandlers` Use `CreateMockDirectoryOptions` instead. -- `CreateMockDirectoryOptions` Use `registerMswTestHooks` instead. - -Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. - -Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` diff --git a/.changeset/light-pianos-exercise.md b/.changeset/light-pianos-exercise.md deleted file mode 100644 index 4bf161eb06..0000000000 --- a/.changeset/light-pianos-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch ---- - -Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws diff --git a/.changeset/little-bulldogs-guess.md b/.changeset/little-bulldogs-guess.md deleted file mode 100644 index 3e646a033f..0000000000 --- a/.changeset/little-bulldogs-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag. diff --git a/.changeset/little-suns-fly.md b/.changeset/little-suns-fly.md deleted file mode 100644 index 887df4be6d..0000000000 --- a/.changeset/little-suns-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Added option to ingest groups based on their group membership in Azure Entra ID diff --git a/.changeset/lovely-planes-shop.md b/.changeset/lovely-planes-shop.md deleted file mode 100644 index 3ee5a57e75..0000000000 --- a/.changeset/lovely-planes-shop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch ---- - -fix: consider broadcast union with user diff --git a/.changeset/lovely-ravens-judge.md b/.changeset/lovely-ravens-judge.md deleted file mode 100644 index 43611556a1..0000000000 --- a/.changeset/lovely-ravens-judge.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -'@backstage/backend-common': minor ---- - -**BREAKING**: Removed the following `Url Reader` deprecated exports: - -- UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; -- AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- BitbucketUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- BitbucketCloudUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- BitbucketServerUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- GithubUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- GitlabUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- GerritUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- GiteaUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- HarnessUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- AwsS3UrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- FetchUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; -- UrlReaders: Import from `@backstage/backend-defaults/urlReader` instead; -- UrlReadersOptions: Import from `@backstage/backend-defaults/urlReader` instead; -- UrlReaderPredicateTuple: Import from `@backstage/backend-defaults/urlReader` instead; -- FromReadableArrayOptions: Import from `@backstage/backend-defaults/urlReader` instead; -- ReaderFactory: Import from `@backstage/backend-defaults/urlReader` instead; -- ReadUrlOptions:Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead; -- ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead; -- ReadUrlResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; -- ReadUrlResponseFactoryFromStreamOptions: Import from `@backstage/backend-defaults/urlReader` instead; -- ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead; -- ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead; -- ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead; -- ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead; -- ReadTreeResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; -- ReadTreeResponseFactoryOptions: Import from `@backstage/backend-defaults/urlReader` instead; -- SearchOptions: Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead; -- SearchResponse: Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead; -- SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead. diff --git a/.changeset/many-years-lie.md b/.changeset/many-years-lie.md deleted file mode 100644 index 8615ddba21..0000000000 --- a/.changeset/many-years-lie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -add i18n for scaffolder diff --git a/.changeset/mean-apricots-perform.md b/.changeset/mean-apricots-perform.md deleted file mode 100644 index c93514cb7f..0000000000 --- a/.changeset/mean-apricots-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes': patch ---- - -Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. diff --git a/.changeset/metal-planes-nail.md b/.changeset/metal-planes-nail.md deleted file mode 100644 index 2cfa12a435..0000000000 --- a/.changeset/metal-planes-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-tasks': patch ---- - -The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. diff --git a/.changeset/metal-rice-call.md b/.changeset/metal-rice-call.md deleted file mode 100644 index 46e30dd26f..0000000000 --- a/.changeset/metal-rice-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -Introduces the HasSubdomainsCard component that displays the subdomains of a given domain diff --git a/.changeset/mighty-apricots-taste.md b/.changeset/mighty-apricots-taste.md deleted file mode 100644 index 80b70020cb..0000000000 --- a/.changeset/mighty-apricots-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. diff --git a/.changeset/mighty-dolls-retire.md b/.changeset/mighty-dolls-retire.md deleted file mode 100644 index b31c443a38..0000000000 --- a/.changeset/mighty-dolls-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export. diff --git a/.changeset/mighty-geckos-kiss.md b/.changeset/mighty-geckos-kiss.md deleted file mode 100644 index 0761d8b257..0000000000 --- a/.changeset/mighty-geckos-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-node': patch ---- - -Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently. diff --git a/.changeset/modern-apes-join.md b/.changeset/modern-apes-join.md deleted file mode 100644 index 452896b924..0000000000 --- a/.changeset/modern-apes-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Added back `type: 'local'` to TechDocs config schema for `publisher` diff --git a/.changeset/modern-brooms-grow.md b/.changeset/modern-brooms-grow.md deleted file mode 100644 index 0aff21dcca..0000000000 --- a/.changeset/modern-brooms-grow.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/core-compat-api': patch -'@backstage/frontend-app-api': patch -'@backstage/frontend-test-utils': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-home': patch -'@backstage/plugin-kubernetes': patch ---- - -Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. diff --git a/.changeset/modern-parrots-protect.md b/.changeset/modern-parrots-protect.md deleted file mode 100644 index bb4df2c248..0000000000 --- a/.changeset/modern-parrots-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -support `ajv-errors` for scaffolder validation to allow for customizing the error messages diff --git a/.changeset/modern-poems-mate.md b/.changeset/modern-poems-mate.md deleted file mode 100644 index 06926daac9..0000000000 --- a/.changeset/modern-poems-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch ---- - -Allow using notifications without users in the catalog diff --git a/.changeset/neat-bears-divide.md b/.changeset/neat-bears-divide.md deleted file mode 100644 index a94b17f74d..0000000000 --- a/.changeset/neat-bears-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -The `env` option of `ConfigSources.default` now correctly allows undefined members. diff --git a/.changeset/neat-gifts-join.md b/.changeset/neat-gifts-join.md deleted file mode 100644 index cd140c4692..0000000000 --- a/.changeset/neat-gifts-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-devtools-backend': patch ---- - -Removed unused code for lockfile analysis. diff --git a/.changeset/neat-socks-cheer.md b/.changeset/neat-socks-cheer.md deleted file mode 100644 index ffc76bec3f..0000000000 --- a/.changeset/neat-socks-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-node': minor ---- - -**BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. diff --git a/.changeset/new-knives-fly.md b/.changeset/new-knives-fly.md deleted file mode 100644 index f9ac945cad..0000000000 --- a/.changeset/new-knives-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-dev-utils': patch ---- - -Fix `EventEmitter` memory leak in the development utilities diff --git a/.changeset/new-scissors-try.md b/.changeset/new-scissors-try.md deleted file mode 100644 index a9e9a45ce7..0000000000 --- a/.changeset/new-scissors-try.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-microsoft-provider': patch ---- - -Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. diff --git a/.changeset/nice-peas-retire.md b/.changeset/nice-peas-retire.md deleted file mode 100644 index 7b7b13706d..0000000000 --- a/.changeset/nice-peas-retire.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/integration': minor -'@backstage/backend-defaults': patch ---- - -Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. diff --git a/.changeset/nine-cherries-decide.md b/.changeset/nine-cherries-decide.md deleted file mode 100644 index b20c24aa85..0000000000 --- a/.changeset/nine-cherries-decide.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -The remaining exports in the package have now been deprecated: - -- `cacheToPluginCacheManager` -- `createLegacyAuthAdapters` -- `LegacyCreateRouter` -- `legacyPlugin` -- `loggerToWinstonLogger` -- `makeLegacyPlugin` - -Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating). diff --git a/.changeset/nine-glasses-nail.md b/.changeset/nine-glasses-nail.md deleted file mode 100644 index 138acce3dd..0000000000 --- a/.changeset/nine-glasses-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-pg': patch ---- - -Removing `@backstage/backend-app-api` dependency diff --git a/.changeset/nine-seahorses-relate.md b/.changeset/nine-seahorses-relate.md deleted file mode 100644 index 9a5bb43823..0000000000 --- a/.changeset/nine-seahorses-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-permission-common': patch ---- - -Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. diff --git a/.changeset/ninety-icons-smile.md b/.changeset/ninety-icons-smile.md deleted file mode 100644 index f137879706..0000000000 --- a/.changeset/ninety-icons-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Remove usage of deprecated functionality from @backstage/config-loader diff --git a/.changeset/odd-books-share.md b/.changeset/odd-books-share.md deleted file mode 100644 index 858a2564ad..0000000000 --- a/.changeset/odd-books-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. diff --git a/.changeset/old-tools-smell.md b/.changeset/old-tools-smell.md deleted file mode 100644 index 4dbd65e779..0000000000 --- a/.changeset/old-tools-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint. diff --git a/.changeset/olive-books-sort.md b/.changeset/olive-books-sort.md deleted file mode 100644 index d7bfd925ce..0000000000 --- a/.changeset/olive-books-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-permission-node': patch ---- - -The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. diff --git a/.changeset/orange-gifts-protect.md b/.changeset/orange-gifts-protect.md deleted file mode 100644 index 1de9e2a6ba..0000000000 --- a/.changeset/orange-gifts-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch ---- - -Added examples for publish:bitbucketServer action and improve its test cases diff --git a/.changeset/perfect-cars-jam.md b/.changeset/perfect-cars-jam.md deleted file mode 100644 index 45166ac823..0000000000 --- a/.changeset/perfect-cars-jam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Changed the way to display entities in `MyGroupsPicker` to use `entityPresentationApi` and make it consistent across scaffolder pickers diff --git a/.changeset/pink-bobcats-explain.md b/.changeset/pink-bobcats-explain.md deleted file mode 100644 index 0274e55369..0000000000 --- a/.changeset/pink-bobcats-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Refactor TechDocs' mkdocs-redirects support. diff --git a/.changeset/pink-gorillas-brake.md b/.changeset/pink-gorillas-brake.md deleted file mode 100644 index 188884f13c..0000000000 --- a/.changeset/pink-gorillas-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Fix extra divider displayed on user list picker component diff --git a/.changeset/plenty-mails-do.md b/.changeset/plenty-mails-do.md deleted file mode 100644 index 6a9e815ebb..0000000000 --- a/.changeset/plenty-mails-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Added `resolvers` prop to `AsyncApiDefinitionWidget`. This allows to override the default http/https resolvers, for example to add authentication to requests to internal schema registries. diff --git a/.changeset/plenty-tools-exist.md b/.changeset/plenty-tools-exist.md deleted file mode 100644 index 0ce22a74f2..0000000000 --- a/.changeset/plenty-tools-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:issue:edit examples diff --git a/.changeset/popular-panthers-hear.md b/.changeset/popular-panthers-hear.md deleted file mode 100644 index c31586ea82..0000000000 --- a/.changeset/popular-panthers-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -TechDocs redirect feature now includes a notification to the user before they are redirected. diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index 335764699a..0000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,344 +0,0 @@ -{ - "mode": "exit", - "tag": "next", - "initialVersions": { - "example-app": "0.2.99", - "@backstage/app-defaults": "1.5.8", - "example-app-next": "0.0.13", - "app-next-example-plugin": "0.0.13", - "example-backend": "0.0.28", - "@backstage/backend-app-api": "0.8.0", - "@backstage/backend-common": "0.23.3", - "@backstage/backend-defaults": "0.4.0", - "@backstage/backend-dev-utils": "0.1.4", - "@backstage/backend-dynamic-feature-service": "0.2.15", - "example-backend-legacy": "0.2.100", - "@backstage/backend-openapi-utils": "0.1.15", - "@backstage/backend-plugin-api": "0.7.0", - "@backstage/backend-tasks": "0.5.27", - "@backstage/backend-test-utils": "0.4.4", - "@backstage/catalog-client": "1.6.5", - "@backstage/catalog-model": "1.5.0", - "@backstage/cli": "0.26.11", - "@backstage/cli-common": "0.1.14", - "@backstage/cli-node": "0.2.7", - "@backstage/codemods": "0.1.49", - "@backstage/config": "1.2.0", - "@backstage/config-loader": "1.8.1", - "@backstage/core-app-api": "1.14.0", - "@backstage/core-compat-api": "0.2.7", - "@backstage/core-components": "0.14.9", - "@backstage/core-plugin-api": "1.9.3", - "@backstage/create-app": "0.5.17", - "@backstage/dev-utils": "1.0.35", - "e2e-test": "0.2.18", - "@backstage/e2e-test-utils": "0.1.1", - "@backstage/errors": "1.2.4", - "@backstage/eslint-plugin": "0.1.8", - "@backstage/frontend-app-api": "0.7.3", - "@backstage/frontend-plugin-api": "0.6.7", - "@backstage/frontend-test-utils": "0.1.10", - "@backstage/integration": "1.13.0", - "@backstage/integration-aws-node": "0.1.12", - "@backstage/integration-react": "1.1.29", - "@backstage/release-manifests": "0.0.11", - "@backstage/repo-tools": "0.9.4", - "@techdocs/cli": "1.8.16", - "techdocs-cli-embedded-app": "0.2.98", - "@backstage/test-utils": "1.5.8", - "@backstage/theme": "0.5.6", - "@backstage/types": "1.1.1", - "@backstage/version-bridge": "1.0.8", - "yarn-plugin-backstage": "0.0.1", - "@backstage/plugin-api-docs": "0.11.7", - "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.7", - "@backstage/plugin-app-backend": "0.3.71", - "@backstage/plugin-app-node": "0.1.22", - "@backstage/plugin-app-visualizer": "0.1.8", - "@backstage/plugin-auth-backend": "0.22.9", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.2.3", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.1.14", - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.1.5", - "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.1.5", - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.1.5", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.2.17", - "@backstage/plugin-auth-backend-module-github-provider": "0.1.19", - "@backstage/plugin-auth-backend-module-gitlab-provider": "0.1.19", - "@backstage/plugin-auth-backend-module-google-provider": "0.1.19", - "@backstage/plugin-auth-backend-module-guest-provider": "0.1.8", - "@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.17", - "@backstage/plugin-auth-backend-module-oauth2-provider": "0.2.3", - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.1.15", - "@backstage/plugin-auth-backend-module-oidc-provider": "0.2.3", - "@backstage/plugin-auth-backend-module-okta-provider": "0.0.15", - "@backstage/plugin-auth-backend-module-onelogin-provider": "0.1.3", - "@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.16", - "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.2.3", - "@backstage/plugin-auth-node": "0.4.17", - "@backstage/plugin-auth-react": "0.1.4", - "@backstage/plugin-bitbucket-cloud-common": "0.2.21", - "@backstage/plugin-catalog": "1.21.1", - "@backstage/plugin-catalog-backend": "1.24.0", - "@backstage/plugin-catalog-backend-module-aws": "0.3.17", - "@backstage/plugin-catalog-backend-module-azure": "0.1.42", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.2.5", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.2.9", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.36", - "@backstage/plugin-catalog-backend-module-gcp": "0.1.23", - "@backstage/plugin-catalog-backend-module-gerrit": "0.1.39", - "@backstage/plugin-catalog-backend-module-github": "0.6.5", - "@backstage/plugin-catalog-backend-module-github-org": "0.1.17", - "@backstage/plugin-catalog-backend-module-gitlab": "0.3.21", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.5", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.27", - "@backstage/plugin-catalog-backend-module-ldap": "0.7.0", - "@backstage/plugin-catalog-backend-module-logs": "0.0.1", - "@backstage/plugin-catalog-backend-module-msgraph": "0.5.30", - "@backstage/plugin-catalog-backend-module-openapi": "0.1.40", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.1.28", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.20", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.4.9", - "@backstage/plugin-catalog-common": "1.0.25", - "@backstage/plugin-catalog-graph": "0.4.7", - "@backstage/plugin-catalog-import": "0.12.1", - "@backstage/plugin-catalog-node": "1.12.4", - "@backstage/plugin-catalog-react": "1.12.2", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.6", - "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.3", - "@backstage/plugin-config-schema": "0.1.57", - "@backstage/plugin-devtools": "0.1.16", - "@backstage/plugin-devtools-backend": "0.3.8", - "@backstage/plugin-devtools-common": "0.1.11", - "@backstage/plugin-events-backend": "0.3.9", - "@backstage/plugin-events-backend-module-aws-sqs": "0.3.8", - "@backstage/plugin-events-backend-module-azure": "0.2.8", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.8", - "@backstage/plugin-events-backend-module-gerrit": "0.2.8", - "@backstage/plugin-events-backend-module-github": "0.2.8", - "@backstage/plugin-events-backend-module-gitlab": "0.2.8", - "@backstage/plugin-events-backend-test-utils": "0.1.32", - "@backstage/plugin-events-node": "0.3.8", - "@internal/plugin-todo-list": "1.0.29", - "@internal/plugin-todo-list-backend": "1.0.29", - "@internal/plugin-todo-list-common": "1.0.20", - "@backstage/plugin-home": "0.7.7", - "@backstage/plugin-home-react": "0.1.15", - "@backstage/plugin-kubernetes": "0.11.12", - "@backstage/plugin-kubernetes-backend": "0.18.3", - "@backstage/plugin-kubernetes-cluster": "0.0.13", - "@backstage/plugin-kubernetes-common": "0.8.1", - "@backstage/plugin-kubernetes-node": "0.1.16", - "@backstage/plugin-kubernetes-react": "0.4.1", - "@backstage/plugin-notifications": "0.2.3", - "@backstage/plugin-notifications-backend": "0.3.3", - "@backstage/plugin-notifications-backend-module-email": "0.1.3", - "@backstage/plugin-notifications-common": "0.0.5", - "@backstage/plugin-notifications-node": "0.2.3", - "@backstage/plugin-org": "0.6.27", - "@backstage/plugin-org-react": "0.1.26", - "@backstage/plugin-permission-backend": "0.5.46", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.19", - "@backstage/plugin-permission-common": "0.8.0", - "@backstage/plugin-permission-node": "0.8.0", - "@backstage/plugin-permission-react": "0.4.24", - "@backstage/plugin-proxy-backend": "0.5.3", - "@backstage/plugin-scaffolder": "1.23.0", - "@backstage/plugin-scaffolder-backend": "1.23.0", - "@backstage/plugin-scaffolder-backend-module-azure": "0.1.14", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.2.12", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.1.12", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.1.12", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.23", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.46", - "@backstage/plugin-scaffolder-backend-module-gcp": "0.1.0", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.14", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.1.12", - "@backstage/plugin-scaffolder-backend-module-github": "0.4.0", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.4.4", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.0.5", - "@backstage/plugin-scaffolder-backend-module-rails": "0.4.39", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.30", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.3.6", - "@backstage/plugin-scaffolder-common": "1.5.4", - "@backstage/plugin-scaffolder-node": "0.4.8", - "@backstage/plugin-scaffolder-node-test-utils": "0.1.9", - "@backstage/plugin-scaffolder-react": "1.10.0", - "@backstage/plugin-search": "1.4.14", - "@backstage/plugin-search-backend": "1.5.14", - "@backstage/plugin-search-backend-module-catalog": "0.1.28", - "@backstage/plugin-search-backend-module-elasticsearch": "1.5.3", - "@backstage/plugin-search-backend-module-explore": "0.1.28", - "@backstage/plugin-search-backend-module-pg": "0.5.32", - "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.1.15", - "@backstage/plugin-search-backend-module-techdocs": "0.1.27", - "@backstage/plugin-search-backend-node": "1.2.27", - "@backstage/plugin-search-common": "1.2.13", - "@backstage/plugin-search-react": "1.7.13", - "@backstage/plugin-signals": "0.0.8", - "@backstage/plugin-signals-backend": "0.1.8", - "@backstage/plugin-signals-node": "0.1.8", - "@backstage/plugin-signals-react": "0.0.4", - "@backstage/plugin-techdocs": "1.10.7", - "@backstage/plugin-techdocs-addons-test-utils": "1.0.35", - "@backstage/plugin-techdocs-backend": "1.10.9", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.12", - "@backstage/plugin-techdocs-node": "1.12.8", - "@backstage/plugin-techdocs-react": "1.2.6", - "@backstage/plugin-user-settings": "0.8.9", - "@backstage/plugin-user-settings-backend": "0.2.21", - "@backstage/plugin-user-settings-common": "0.0.1", - "@backstage/plugin-techdocs-common": "0.0.0" - }, - "changesets": [ - "angry-dolphins-camp", - "big-countries-wait", - "big-eagles-grab", - "blue-pumas-cheer", - "breezy-cats-kiss", - "breezy-jeans-tie", - "breezy-rings-fly", - "bright-donkeys-buy", - "bright-trainers-brake", - "calm-crabs-drop", - "chilly-days-peel", - "chilly-trains-sleep", - "clean-kids-applaud", - "clever-pans-brake", - "cool-insects-remember", - "cool-schools-vanish", - "create-app-1722413762", - "cuddly-zebras-crash", - "curvy-pillows-joke", - "cyan-shrimps-push", - "dry-squids-tap", - "dull-ghosts-double", - "early-trees-dance", - "eighty-emus-leave", - "eighty-jokes-deny", - "eighty-mirrors-flow", - "fair-hairs-mix", - "fair-pumas-hang", - "fast-bulldogs-relax", - "few-wasps-hug", - "five-mangos-agree", - "flat-papayas-push", - "flat-plums-grow", - "forty-ties-agree", - "forty-ties-disagree", - "fresh-timers-walk", - "friendly-cherries-applaud", - "friendly-chicken-cry", - "friendly-feet-refuse", - "funny-bears-sort", - "funny-wolves-learn", - "gentle-dryers-smile", - "good-steaks-report", - "green-planets-reflect", - "grumpy-owls-suffer", - "healthy-fireants-jump", - "healthy-timers-divide", - "heavy-numbers-love", - "hip-fishes-guess", - "hip-hairs-exist", - "itchy-experts-tie", - "khaki-lamps-peel", - "late-games-protect", - "light-pianos-exercise", - "little-bulldogs-guess", - "little-suns-fly", - "mean-apricots-perform", - "metal-planes-nail", - "metal-rice-call", - "mighty-apricots-taste", - "mighty-dolls-retire", - "mighty-geckos-kiss", - "modern-parrots-protect", - "modern-poems-mate", - "neat-bears-divide", - "neat-gifts-join", - "neat-socks-cheer", - "new-scissors-try", - "nice-peas-retire", - "nine-cherries-decide", - "nine-glasses-nail", - "nine-seahorses-relate", - "ninety-icons-smile", - "odd-books-share", - "old-tools-smell", - "olive-books-sort", - "orange-gifts-protect", - "perfect-cars-jam", - "pink-gorillas-brake", - "plenty-tools-exist", - "popular-panthers-hear", - "purple-carrots-crash", - "quick-roses-juggle", - "rare-foxes-compete", - "real-lizards-sit", - "red-radios-promise", - "renovate-147ac48", - "renovate-f04beb1", - "rich-mugs-dress", - "selfish-bees-think", - "seven-days-film", - "seven-eggs-admire", - "shaggy-dodos-applaud", - "shaggy-mugs-return", - "shy-games-poke", - "shy-waves-share", - "silly-candles-sin", - "silly-cycles-tan", - "silly-scissors-turn", - "silver-pillows-begin", - "six-mails-smell", - "six-rats-kick", - "sixty-kiwis-poke", - "slow-ducks-rush", - "slow-ligers-drum", - "slow-rocks-end", - "slow-toes-jog", - "small-bottles-cough", - "small-ears-poke", - "small-spoons-shout", - "smooth-countries-relate", - "soft-gorillas-refuse", - "spicy-lies-listen", - "spicy-planets-provide", - "spotty-planets-accept", - "strange-papayas-beg", - "strong-chicken-kiss", - "strong-otters-compete", - "stupid-dots-relate", - "sweet-oranges-buy", - "swift-kings-sparkle", - "tall-snakes-fix", - "tasty-ads-rescue", - "ten-penguins-roll", - "thick-hotels-know", - "thick-squids-drive", - "thin-carrots-eat", - "thirty-adults-grab", - "thirty-paws-hope", - "tiny-dodos-prove-2", - "tiny-dodos-prove", - "tiny-oranges-pretend", - "tough-goats-hang", - "tough-lies-repair", - "tricky-ducks-juggle", - "tricky-rules-destroy", - "two-emus-work", - "violet-jokes-wave", - "warm-monkeys-marry", - "weak-jobs-joke", - "wicked-bobcats-teach", - "wild-eggs-exist", - "wise-spiders-walk", - "witty-bears-behave", - "witty-geese-battle", - "witty-queens-run", - "witty-timers-marry", - "young-birds-push", - "young-games-visit", - "young-peaches-shake" - ] -} diff --git a/.changeset/purple-carrots-crash.md b/.changeset/purple-carrots-crash.md deleted file mode 100644 index 62f90677b8..0000000000 --- a/.changeset/purple-carrots-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. diff --git a/.changeset/quick-roses-juggle.md b/.changeset/quick-roses-juggle.md deleted file mode 100644 index 981bd8591a..0000000000 --- a/.changeset/quick-roses-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-common': minor ---- - -Initial release of the techdocs-common package. diff --git a/.changeset/rare-foxes-compete.md b/.changeset/rare-foxes-compete.md deleted file mode 100644 index 64dd9b557e..0000000000 --- a/.changeset/rare-foxes-compete.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Extensions have been changed to be declared with an array of inputs and outputs, rather than a map of named data refs. This change was made to reduce confusion around the role of the input and output names, as well as enable more powerful APIs for overriding extensions. - -An extension that was previously declared like this: - -```tsx -const exampleExtension = createExtension({ - name: 'example', - inputs: { - items: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( -
    - Example - {inputs.items.map(item => { - return
    {item.output.element}
    ; - })} -
    - ), - }; - }, -}); -``` - -Should be migrated to the following: - -```tsx -const exampleExtension = createExtension({ - name: 'example', - inputs: { - items: createExtensionInput([coreExtensionData.reactElement]), - }, - output: [coreExtensionData.reactElement], - factory({ inputs }) { - return [ - coreExtensionData.reactElement( -
    - Example - {inputs.items.map(item => { - return
    {item.get(coreExtensionData.reactElement)}
    ; - })} -
    , - ), - ]; - }, -}); -``` diff --git a/.changeset/rare-maps-wave.md b/.changeset/rare-maps-wave.md deleted file mode 100644 index 7d3e074eae..0000000000 --- a/.changeset/rare-maps-wave.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch -'@backstage/core-components': patch ---- - -Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv diff --git a/.changeset/real-lizards-sit.md b/.changeset/real-lizards-sit.md deleted file mode 100644 index 747f041508..0000000000 --- a/.changeset/real-lizards-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix undefined in the title of Scaffolder Runs on the page load diff --git a/.changeset/red-radios-promise.md b/.changeset/red-radios-promise.md deleted file mode 100644 index ea876c3480..0000000000 --- a/.changeset/red-radios-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-gitlab': patch ---- - -Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. diff --git a/.changeset/renovate-147ac48.md b/.changeset/renovate-147ac48.md deleted file mode 100644 index 257255dff1..0000000000 --- a/.changeset/renovate-147ac48.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@graphiql/react` to `^0.23.0`. diff --git a/.changeset/renovate-f04beb1.md b/.changeset/renovate-f04beb1.md deleted file mode 100644 index 12d24ccc53..0000000000 --- a/.changeset/renovate-f04beb1.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-explore': patch ---- - -Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.4`. diff --git a/.changeset/rich-mugs-dress.md b/.changeset/rich-mugs-dress.md deleted file mode 100644 index 5008e33a70..0000000000 --- a/.changeset/rich-mugs-dress.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and -`ConfigSources.defaultForTargets`, which results in omission of a ConfigSource -for the default app-config.yaml configuration file if it's not present. diff --git a/.changeset/selfish-bees-think.md b/.changeset/selfish-bees-think.md deleted file mode 100644 index 15598ea551..0000000000 --- a/.changeset/selfish-bees-think.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`. diff --git a/.changeset/seven-days-film.md b/.changeset/seven-days-film.md deleted file mode 100644 index 9d757b0611..0000000000 --- a/.changeset/seven-days-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. diff --git a/.changeset/seven-eggs-admire.md b/.changeset/seven-eggs-admire.md deleted file mode 100644 index d68b38e2d7..0000000000 --- a/.changeset/seven-eggs-admire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data diff --git a/.changeset/shaggy-dodos-applaud.md b/.changeset/shaggy-dodos-applaud.md deleted file mode 100644 index b7f968aa39..0000000000 --- a/.changeset/shaggy-dodos-applaud.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. diff --git a/.changeset/shaggy-mugs-return.md b/.changeset/shaggy-mugs-return.md deleted file mode 100644 index 6af5d1d18f..0000000000 --- a/.changeset/shaggy-mugs-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Update configuration schema to match actual behavior diff --git a/.changeset/shy-games-poke.md b/.changeset/shy-games-poke.md deleted file mode 100644 index 1d2abff39c..0000000000 --- a/.changeset/shy-games-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Add support for stream transport for debugging purposes diff --git a/.changeset/shy-waves-share.md b/.changeset/shy-waves-share.md deleted file mode 100644 index 861590fd01..0000000000 --- a/.changeset/shy-waves-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. diff --git a/.changeset/silent-camels-walk.md b/.changeset/silent-camels-walk.md deleted file mode 100644 index 244a615335..0000000000 --- a/.changeset/silent-camels-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Added utilities for converting existing entity card and content extensions to the new frontend system. This is in particular useful when used in combination with the new `convertLegacyPlugin` utility from `@backstage/core-compat-api`. diff --git a/.changeset/silly-candles-sin.md b/.changeset/silly-candles-sin.md deleted file mode 100644 index b1d55113fd..0000000000 --- a/.changeset/silly-candles-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. diff --git a/.changeset/silly-cycles-tan.md b/.changeset/silly-cycles-tan.md deleted file mode 100644 index 517ce526ea..0000000000 --- a/.changeset/silly-cycles-tan.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. -This was already documented and supported by the plugin. diff --git a/.changeset/silly-scissors-turn.md b/.changeset/silly-scissors-turn.md deleted file mode 100644 index 985a6fb4fd..0000000000 --- a/.changeset/silly-scissors-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Included permission config and enabled it out of the box diff --git a/.changeset/silver-pillows-begin.md b/.changeset/silver-pillows-begin.md deleted file mode 100644 index 8fe1ccc230..0000000000 --- a/.changeset/silver-pillows-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Internal type refactor. diff --git a/.changeset/six-mails-smell.md b/.changeset/six-mails-smell.md deleted file mode 100644 index 3845c72626..0000000000 --- a/.changeset/six-mails-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Support merging of `inputs` in extension blueprints, but stop merging `output`. In addition, the original factory in extension blueprints now returns a data container that both provides access to the returned data, but can also be forwarded as output. diff --git a/.changeset/six-rats-kick.md b/.changeset/six-rats-kick.md deleted file mode 100644 index 23958a2502..0000000000 --- a/.changeset/six-rats-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:projectAccessToken:create example diff --git a/.changeset/sixty-kiwis-poke.md b/.changeset/sixty-kiwis-poke.md deleted file mode 100644 index 5281c6ade0..0000000000 --- a/.changeset/sixty-kiwis-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -use formatted query to prevent chance of SQL-injection diff --git a/.changeset/slow-ducks-rush.md b/.changeset/slow-ducks-rush.md deleted file mode 100644 index c09f883e9c..0000000000 --- a/.changeset/slow-ducks-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-compat-api': patch ---- - -Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. diff --git a/.changeset/slow-ligers-drum.md b/.changeset/slow-ligers-drum.md deleted file mode 100644 index 867cc07cee..0000000000 --- a/.changeset/slow-ligers-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Fix null check in `isJsonObject` utility function for scaffolder review state component diff --git a/.changeset/slow-rocks-end.md b/.changeset/slow-rocks-end.md deleted file mode 100644 index f2633c9924..0000000000 --- a/.changeset/slow-rocks-end.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated dependency `@module-federation/enhanced` to `0.3.1` diff --git a/.changeset/slow-toes-jog.md b/.changeset/slow-toes-jog.md deleted file mode 100644 index 245feab544..0000000000 --- a/.changeset/slow-toes-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish diff --git a/.changeset/small-bottles-cough.md b/.changeset/small-bottles-cough.md deleted file mode 100644 index 40098a84c7..0000000000 --- a/.changeset/small-bottles-cough.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -'@backstage/backend-plugin-api': minor ---- - -The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. - -We're looking for ways to make it possible to augment services without the need to replace the entire service. - -Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: - -```diff -// @backstage/backend-defaults - -+ export const urlReaderFactoriesServiceRef = createServiceRef({ -+ id: 'core.urlReader.factories', -+ scope: 'plugin', -+ multiton: true, -+ }); - -... - -export const urlReaderServiceFactory = createServiceFactory({ - service: coreServices.urlReader, - deps: { - config: coreServices.rootConfig, - logger: coreServices.logger, -+ factories: urlReaderFactoriesServiceRef, - }, -- async factory({ config, logger }) { -+ async factory({ config, logger, factories }) { - return UrlReaders.default({ - config, - logger, -+ factories, - }); - }, -}); -``` - -With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: - -```ts -// packages/backend/index.ts -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; -... - -backend.add(createServiceFactory({ - service: urlReaderFactoriesServiceRef, - deps: {}, - async factory() { - return CustomUrlReader.factory; - }, -})); - -... - -``` diff --git a/.changeset/small-ears-poke.md b/.changeset/small-ears-poke.md deleted file mode 100644 index de906a8643..0000000000 --- a/.changeset/small-ears-poke.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/frontend-plugin-api': minor ---- - -**BREAKING**: All types of route refs are always considered optional by `useRouteRef`, which means the caller must always handle a potential `undefined` return value. Related to this change, the `optional` option from `createExternalRouteRef` has been removed, since it is no longer necessary. - -This is released as an immediate breaking change as we expect the usage of the new route refs to be extremely low or zero, since plugins that support the new system will still use route refs and `useRouteRef` from `@backstage/core-plugin-api` in combination with `convertLegacyRouteRef` from `@backstage/core-compat-api`. diff --git a/.changeset/small-spoons-shout.md b/.changeset/small-spoons-shout.md deleted file mode 100644 index 76c209edc6..0000000000 --- a/.changeset/small-spoons-shout.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/cli': minor ---- - -**BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. - -The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files. - -The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic. - -The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches. - -The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future. diff --git a/.changeset/smooth-countries-relate.md b/.changeset/smooth-countries-relate.md deleted file mode 100644 index 5e86ee8ab5..0000000000 --- a/.changeset/smooth-countries-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch ---- - -Use `entityPresentationApi` for the node title and the icon. diff --git a/.changeset/soft-files-greet.md b/.changeset/soft-files-greet.md deleted file mode 100644 index 3d83790f0a..0000000000 --- a/.changeset/soft-files-greet.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@backstage/backend-tasks': minor ---- - -This package is deprecated and will be removed in a near future, follow the instructions below to stop using it: - -- `TaskScheduler`: Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults; -- `TaskRunner`: Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead; -- `TaskFunction`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; -- `TaskDescriptor`: Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead; -- `TaskInvocationDefinition`: Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead; -- `TaskScheduleDefinition`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; -- `TaskScheduleDefinitionConfig`: Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead; -- `PluginTaskScheduler`: Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`); -- `readTaskScheduleDefinitionFromConfig`: Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead; -- `HumanDuration`: Import `TypesHumanDuration` from `@backstage/types` instead. diff --git a/.changeset/soft-gorillas-refuse.md b/.changeset/soft-gorillas-refuse.md deleted file mode 100644 index 323c4d20d2..0000000000 --- a/.changeset/soft-gorillas-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. diff --git a/.changeset/spicy-jars-hear.md b/.changeset/spicy-jars-hear.md deleted file mode 100644 index 9006501ee1..0000000000 --- a/.changeset/spicy-jars-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch ---- - -Deprecate `createRouter` and its options in favour of the new backend system. diff --git a/.changeset/spicy-lies-listen.md b/.changeset/spicy-lies-listen.md deleted file mode 100644 index 6bc5e95846..0000000000 --- a/.changeset/spicy-lies-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix helper text margin for scaffolder EntityNamePicker and EntityTagsPicker when using outlined text field diff --git a/.changeset/spicy-planets-provide.md b/.changeset/spicy-planets-provide.md deleted file mode 100644 index f2929df850..0000000000 --- a/.changeset/spicy-planets-provide.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': minor ---- - -**BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. - -Required changes and example usage: - -```diff -import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; -import { Notification } from '@backstage/plugin-notifications-common'; -+import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` -export const notificationsModuleEmailDecorator = createBackendModule({ - pluginId: 'notifications', - moduleId: 'email.templates', - register(reg) { - reg.registerInit({ - deps: { - emailTemplates: notificationsEmailTemplateExtensionPoint, - }, - async init({ emailTemplates }) { - emailTemplates.setTemplateRenderer({ -- getSubject(notification) { -+ async getSubject(notification) { -- return `New notification from ${notification.source}`; -+ const subject = await getNotificationSubject(notification); -+ return `New notification from ${subject}`; - }, -- getText(notification) { -+ async getText(notification) { -- return notification.content; -+ const text = await getNotificationTextContent(notification); -+ return text; - }, -- getHtml(notification) { -+ async getHtml(notification) { -- return `

    ${notification.content}

    `; -+ const html = await getNotificationHtmlContent(notification); -+ return html; - }, - }); - }, - }); - }, -}); -``` diff --git a/.changeset/spotty-planets-accept.md b/.changeset/spotty-planets-accept.md deleted file mode 100644 index 7342f89bd1..0000000000 --- a/.changeset/spotty-planets-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Cleaned up codebase of RepoUrlPicker diff --git a/.changeset/strange-papayas-beg.md b/.changeset/strange-papayas-beg.md deleted file mode 100644 index 6ce33fb029..0000000000 --- a/.changeset/strange-papayas-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:pipeline:trigger examples diff --git a/.changeset/strong-chicken-kiss.md b/.changeset/strong-chicken-kiss.md deleted file mode 100644 index 26f667c5ab..0000000000 --- a/.changeset/strong-chicken-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': minor ---- - -Support for Cloudflare Custom Headers and Custom Cookie Auth Name diff --git a/.changeset/strong-otters-compete.md b/.changeset/strong-otters-compete.md deleted file mode 100644 index fdcb982b88..0000000000 --- a/.changeset/strong-otters-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. diff --git a/.changeset/stupid-dots-relate.md b/.changeset/stupid-dots-relate.md deleted file mode 100644 index a83777bff9..0000000000 --- a/.changeset/stupid-dots-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch ---- - -Add examples for `fetch:cookiecutter` scaffolder action & improve related tests diff --git a/.changeset/sweet-oranges-buy.md b/.changeset/sweet-oranges-buy.md deleted file mode 100644 index d9f3343e3d..0000000000 --- a/.changeset/sweet-oranges-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. diff --git a/.changeset/swift-dragons-know.md b/.changeset/swift-dragons-know.md deleted file mode 100644 index 7e82994eca..0000000000 --- a/.changeset/swift-dragons-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-visualizer': patch ---- - -Fixing issue with the visualizer crashing when clicking on the detailed view because of `routeRef` parameters diff --git a/.changeset/swift-kings-sparkle.md b/.changeset/swift-kings-sparkle.md deleted file mode 100644 index 84ceae962b..0000000000 --- a/.changeset/swift-kings-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-aws': patch ---- - -`AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` diff --git a/.changeset/tall-snakes-fix.md b/.changeset/tall-snakes-fix.md deleted file mode 100644 index 351c781375..0000000000 --- a/.changeset/tall-snakes-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -fix typo in `getPluginRequestToken` comments diff --git a/.changeset/tame-doors-think.md b/.changeset/tame-doors-think.md deleted file mode 100644 index 2e2ecabd35..0000000000 --- a/.changeset/tame-doors-think.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/backend-plugin-api': minor ---- - -**BREAKING** Removed the following deprecated types: - -- `ServiceRefConfig` use `ServiceRefOptions` -- `RootServiceFactoryConfig` use `RootServiceFactoryOptions` -- `PluginServiceFactoryConfig` use `PluginServiceFactoryOptions` diff --git a/.changeset/tasty-ads-rescue.md b/.changeset/tasty-ads-rescue.md deleted file mode 100644 index d522856bdd..0000000000 --- a/.changeset/tasty-ads-rescue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Avoid excessive numbers of error listeners on cache clients diff --git a/.changeset/ten-penguins-roll.md b/.changeset/ten-penguins-roll.md deleted file mode 100644 index 152bb1524b..0000000000 --- a/.changeset/ten-penguins-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': minor ---- - -By default, set notification as read when opening snackbar or web notification link diff --git a/.changeset/thick-hotels-know.md b/.changeset/thick-hotels-know.md deleted file mode 100644 index f0c7129256..0000000000 --- a/.changeset/thick-hotels-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix scaffolder action `catalog:write` to write to directories that don't already exist diff --git a/.changeset/thick-squids-drive.md b/.changeset/thick-squids-drive.md deleted file mode 100644 index 0e798063c0..0000000000 --- a/.changeset/thick-squids-drive.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch -'@backstage/frontend-test-utils': patch ---- - -Added support for being able to override extension definitions. - -```tsx -const TestCard = EntityCardBlueprint.make({ - ... -}); - -TestCard.override({ - // override attachment points - attachTo: { id: 'something-else', input: 'overridden' }, - // extend the config schema - config: { - schema: { - newConfig: z => z.string().optional(), - } - }, - // override factory - *factory(originalFactory, { inputs, config }){ - const originalOutput = originalFactory(); - - yield coreExentsionData.reactElement( - - {originalOutput.get(coreExentsionData.reactElement)} - - ); - } -}); - -``` diff --git a/.changeset/thin-carrots-eat.md b/.changeset/thin-carrots-eat.md deleted file mode 100644 index 570f21f83d..0000000000 --- a/.changeset/thin-carrots-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added examples for action github:pages and improved its test cases diff --git a/.changeset/thirty-adults-grab.md b/.changeset/thirty-adults-grab.md deleted file mode 100644 index ed986a8289..0000000000 --- a/.changeset/thirty-adults-grab.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-catalog-node': patch ---- - -Explicit declare if the service ref accepts `single` or `multiple` implementations. diff --git a/.changeset/thirty-balloons-end.md b/.changeset/thirty-balloons-end.md deleted file mode 100644 index f6e252988e..0000000000 --- a/.changeset/thirty-balloons-end.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-compat-api': patch ---- - -Added new utilities for converting legacy plugins and extensions to the new system. The `convertLegacyPlugin` option will convert an existing plugin to the new system, although you need to supply extensions for the plugin yourself. To help out with this, there is also a new `convertLegacyPageExtension` which converts an existing page extension to the new system. diff --git a/.changeset/thirty-dogs-wave.md b/.changeset/thirty-dogs-wave.md deleted file mode 100644 index b8c4728a32..0000000000 --- a/.changeset/thirty-dogs-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Deprecated `createRouter` and its router options in favour of the new backend system. diff --git a/.changeset/thirty-paws-hope.md b/.changeset/thirty-paws-hope.md deleted file mode 100644 index 0de607f331..0000000000 --- a/.changeset/thirty-paws-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. diff --git a/.changeset/three-kiwis-turn.md b/.changeset/three-kiwis-turn.md deleted file mode 100644 index eb67cbbbdc..0000000000 --- a/.changeset/three-kiwis-turn.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Deprecate the `.render` method of the `createExtensionTester` in favour of using `renderInTestApp` directly. - -```tsx -import { - renderInTestApp, - createExtensionTester, -} from '@backstage/frontend-test-utils'; - -const tester = createExtensionTester(extension); - -const { getByTestId } = renderInTestApp(tester.reactElement()); - -// or if you're not using `coreExtensionData.reactElement` as the output ref -const { getByTestId } = renderInTestApp(tester.get(myComponentRef)); -``` diff --git a/.changeset/tidy-guests-battle.md b/.changeset/tidy-guests-battle.md deleted file mode 100644 index df86b02d1d..0000000000 --- a/.changeset/tidy-guests-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -export `createConfigSecretEnumerator` from `@backstage/backend-common` instead of `@backstage/backend-app-api`. diff --git a/.changeset/tiny-dodos-prove-2.md b/.changeset/tiny-dodos-prove-2.md deleted file mode 100644 index 91ac021c67..0000000000 --- a/.changeset/tiny-dodos-prove-2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. diff --git a/.changeset/tiny-dodos-prove.md b/.changeset/tiny-dodos-prove.md deleted file mode 100644 index 6392e5050e..0000000000 --- a/.changeset/tiny-dodos-prove.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Refactor `.make` method on Blueprints into two different methods, `.make` and `.makeWithOverrides`. - -When using `createExtensionBlueprint` you can define parameters for the factory function, if you wish to take advantage of these parameters you should use `.make` when creating an extension instance of a Blueprint. If you wish to override more things other than the standard `attachTo`, `name`, `namespace` then you should use `.makeWithOverrides` instead. - -`.make` is reserved for simple creation of extension instances from Blueprints using higher level parameters, whereas `.makeWithOverrides` is lower level and you have more control over the final extension. diff --git a/.changeset/tiny-oranges-pretend.md b/.changeset/tiny-oranges-pretend.md deleted file mode 100644 index 87227fb962..0000000000 --- a/.changeset/tiny-oranges-pretend.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release. - -For example, the following code: - -```ts -export const myExtension = createExtensionDataRef('my-plugin.my-data'); -``` - -Should be updated to the following: - -```ts -export const myExtension = createExtensionDataRef().with({ - id: 'my-plugin.my-data', -}); -``` diff --git a/.changeset/tough-goats-hang.md b/.changeset/tough-goats-hang.md deleted file mode 100644 index 2608c03b2e..0000000000 --- a/.changeset/tough-goats-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Added field extension `RepoBranchPicker` that supports autocompletion for Bitbucket diff --git a/.changeset/tough-lies-repair.md b/.changeset/tough-lies-repair.md deleted file mode 100644 index b2f848e292..0000000000 --- a/.changeset/tough-lies-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host diff --git a/.changeset/tricky-ducks-juggle.md b/.changeset/tricky-ducks-juggle.md deleted file mode 100644 index 48c0d215bd..0000000000 --- a/.changeset/tricky-ducks-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added configuration for the `packages` options to config schema diff --git a/.changeset/tricky-rules-destroy.md b/.changeset/tricky-rules-destroy.md deleted file mode 100644 index 196702f8c4..0000000000 --- a/.changeset/tricky-rules-destroy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-node': patch ---- - -Fix TechDocs Edit URL for nested docs diff --git a/.changeset/twelve-peaches-develop.md b/.changeset/twelve-peaches-develop.md deleted file mode 100644 index 804c352ba4..0000000000 --- a/.changeset/twelve-peaches-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Fix label related accessibility issues with `FavorityEntity` diff --git a/.changeset/two-crabs-call.md b/.changeset/two-crabs-call.md deleted file mode 100644 index 8f6979fac4..0000000000 --- a/.changeset/two-crabs-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-devtools-backend': patch ---- - -Deprecated `createRouter` and its router options in favour of the new backend system. diff --git a/.changeset/two-emus-work.md b/.changeset/two-emus-work.md deleted file mode 100644 index 3c60086043..0000000000 --- a/.changeset/two-emus-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -New command now supports setting package license diff --git a/.changeset/violet-jokes-wave.md b/.changeset/violet-jokes-wave.md deleted file mode 100644 index 623cff2583..0000000000 --- a/.changeset/violet-jokes-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fix for `repo build --all` not properly detecting the experimental public entry point. diff --git a/.changeset/warm-moles-appear.md b/.changeset/warm-moles-appear.md deleted file mode 100644 index c2809e606f..0000000000 --- a/.changeset/warm-moles-appear.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Fix GitHub `repository` event support. - -- `$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. -- `$.repository.url` is not always returning the expected and required value. Use `$.repository.html_url` instead. diff --git a/.changeset/warm-monkeys-marry.md b/.changeset/warm-monkeys-marry.md deleted file mode 100644 index 5ea8deae01..0000000000 --- a/.changeset/warm-monkeys-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-sentry': patch ---- - -Added test cases for sentry:project:create examples diff --git a/.changeset/weak-jobs-joke.md b/.changeset/weak-jobs-joke.md deleted file mode 100644 index 8127605f07..0000000000 --- a/.changeset/weak-jobs-joke.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Add `tableOptions` to all tables and additionally `title` to API tables. diff --git a/.changeset/wicked-bobcats-teach.md b/.changeset/wicked-bobcats-teach.md deleted file mode 100644 index 041e336f7d..0000000000 --- a/.changeset/wicked-bobcats-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-react': patch ---- - -feat: Hide visibility of CookieAuthRedirect diff --git a/.changeset/wild-eggs-exist.md b/.changeset/wild-eggs-exist.md deleted file mode 100644 index 68c49e343b..0000000000 --- a/.changeset/wild-eggs-exist.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Add custom action for merge request: **auto** - -The **Auto** action selects the committed action between _create_ and _update_. - -The **Auto** action fetches files using the **/projects/repository/tree endpoint**. -After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**. diff --git a/.changeset/wise-spiders-walk.md b/.changeset/wise-spiders-walk.md deleted file mode 100644 index 65793041c5..0000000000 --- a/.changeset/wise-spiders-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. diff --git a/.changeset/witty-bears-behave.md b/.changeset/witty-bears-behave.md deleted file mode 100644 index 174467b9eb..0000000000 --- a/.changeset/witty-bears-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls diff --git a/.changeset/witty-geese-battle.md b/.changeset/witty-geese-battle.md deleted file mode 100644 index 082784cc8b..0000000000 --- a/.changeset/witty-geese-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. diff --git a/.changeset/witty-queens-run.md b/.changeset/witty-queens-run.md deleted file mode 100644 index 2d869caa91..0000000000 --- a/.changeset/witty-queens-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Notification email processor supports allowing or denying specific email addresses from receiving notifications diff --git a/.changeset/witty-timers-marry.md b/.changeset/witty-timers-marry.md deleted file mode 100644 index 4193acb6fc..0000000000 --- a/.changeset/witty-timers-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bitbucket-cloud-common': patch ---- - -Added method `listBranchesByRepository` to `BitbucketCloudClient` diff --git a/.changeset/young-birds-push.md b/.changeset/young-birds-push.md deleted file mode 100644 index 54afa48aaf..0000000000 --- a/.changeset/young-birds-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -Introduce an optional spec.type attribute on the Domain and System entity kinds diff --git a/.changeset/young-games-visit.md b/.changeset/young-games-visit.md deleted file mode 100644 index 467abcec86..0000000000 --- a/.changeset/young-games-visit.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch -'@backstage/frontend-test-utils': patch -'@backstage/frontend-app-api': patch -'@backstage/core-compat-api': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-search': patch ---- - -Added config input type to the extensions diff --git a/.changeset/young-peaches-shake.md b/.changeset/young-peaches-shake.md deleted file mode 100644 index 75c93f8f3b..0000000000 --- a/.changeset/young-peaches-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': minor ---- - -Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). diff --git a/docs/releases/v1.30.0-changelog.md b/docs/releases/v1.30.0-changelog.md new file mode 100644 index 0000000000..608d94e711 --- /dev/null +++ b/docs/releases/v1.30.0-changelog.md @@ -0,0 +1,3093 @@ +# Release v1.30.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0](https://backstage.github.io/upgrade-helper/?to=1.30.0) + +## @backstage/backend-app-api@0.9.0 + +### Minor Changes + +- da4fde5: **BREAKING**: Removed several deprecated service factories. These can instead be imported from `@backstage/backend-defaults` package. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- 389f5a4: Remove deprecated `urlReaderServiceFactory`, please import from `@backstage/backend-defaults/urlReader` instead. + +### Patch Changes + +- 8b13183: Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 7c5f3b0: Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. +- 80a0737: Added configuration for the `packages` options to config schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-common@0.24.0 + +### Minor Changes + +- 389f5a4: **BREAKING**: Removed the following `Url Reader` deprecated exports: + + - UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; + - AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketCloudUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketServerUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GithubUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GitlabUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GerritUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GiteaUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - HarnessUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - AwsS3UrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - FetchUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaders: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReadersOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaderPredicateTuple: Import from `@backstage/backend-defaults/urlReader` instead; + - FromReadableArrayOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReaderFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlOptions:Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlResponseFactoryFromStreamOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeResponseFactoryOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead. + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- 6795e33: This package is marked as `deprecated` and will be removed in a near future, please follow the deprecated instructions for the exports you still use. + +- 7e13b7a: The remaining exports in the package have now been deprecated: + + - `cacheToPluginCacheManager` + - `createLegacyAuthAdapters` + - `LegacyCreateRouter` + - `legacyPlugin` + - `loggerToWinstonLogger` + - `makeLegacyPlugin` + + Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating). + +- ddde5fe: Internal type refactor. + +- b63d378: export `createConfigSecretEnumerator` from `@backstage/backend-common` instead of `@backstage/backend-app-api`. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-dynamic-feature-service@0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/backend-plugin-api@0.8.0 + +### Minor Changes + +- 389f5a4: **BREAKING** Deleted the following deprecated `UrlReader` exports + + - ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` instead. + +- 7c5f3b0: The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. + + We're looking for ways to make it possible to augment services without the need to replace the entire service. + + Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: + + ```diff + // @backstage/backend-defaults + + + export const urlReaderFactoriesServiceRef = createServiceRef({ + + id: 'core.urlReader.factories', + + scope: 'plugin', + + multiton: true, + + }); + + ... + + export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + + factories: urlReaderFactoriesServiceRef, + }, + - async factory({ config, logger }) { + + async factory({ config, logger, factories }) { + return UrlReaders.default({ + config, + logger, + + factories, + }); + }, + }); + ``` + + With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: + + ```ts + // packages/backend/index.ts + import { createServiceFactory } from '@backstage/backend-plugin-api'; + import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; + ... + + backend.add(createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, + })); + + ... + + ``` + +- c99c620: **BREAKING** Removed the following deprecated types: + + - `ServiceRefConfig` use `ServiceRefOptions` + - `RootServiceFactoryConfig` use `RootServiceFactoryOptions` + - `PluginServiceFactoryConfig` use `PluginServiceFactoryOptions` + +### Patch Changes + +- 6061061: Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. + +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. + +- 8b13183: Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. + + A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. + + Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. + + ```ts + const searchLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + *loader({ config }) { + // Example of a custom config flag to enable search + if (config.getOptionalString('customFeatureToggle.search')) { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + } + }, + }); + ``` + +- ddde5fe: Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. + +- f011d1b: fix typo in `getPluginRequestToken` comments + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-tasks@0.6.0 + +### Minor Changes + +- fc24d9e: This package is deprecated and will be removed in a near future, follow the instructions below to stop using it: + + - `TaskScheduler`: Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from \`@backstage/backend-defaults; + - `TaskRunner`: Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead; + - `TaskFunction`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskDescriptor`: Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead; + - `TaskInvocationDefinition`: Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinition`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinitionConfig`: Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead; + - `PluginTaskScheduler`: Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`); + - `readTaskScheduleDefinitionFromConfig`: Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead; + - `HumanDuration`: Import `TypesHumanDuration` from `@backstage/types` instead. + +### Patch Changes + +- ba9abf4: The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.5.0 + +### Minor Changes + +- 861f162: **BREAKING**: Removed these deprecated helpers: + + - `setupRequestMockHandlers` Use `CreateMockDirectoryOptions` instead. + - `CreateMockDirectoryOptions` Use `registerMswTestHooks` instead. + + Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. + + Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- b63d378: Update internal imports +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/catalog-model@1.6.0 + +### Minor Changes + +- 34fa803: Introduce an optional spec.type attribute on the Domain and System entity kinds + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/cli@0.27.0 + +### Minor Changes + +- 32a38e1: **BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. + + The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files. + + The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic. + + The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches. + + The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future. + +### Patch Changes + +- 7eb08a6: Add frontend-dynamic-container role to eslint config factory +- b2d97fd: Fixing loading of additional config files with new `ConfigSources` +- fbc7819: Use ES2022 in CLI bundler +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. +- ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag. +- 239dffc: Remove usage of deprecated functionality from @backstage/config-loader +- e6e7d86: Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. +- 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` +- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. +- adabb40: New command now supports setting package license +- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point. +- Updated dependencies + - @backstage/config-loader@1.9.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/config-loader@1.9.0 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and + `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource + for the default app-config.yaml configuration file if it's not present. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/frontend-app-api@0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 7777b5f: Support icon overriding with the new `IconBundleBlueprint` API. +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.7.0 + +### Minor Changes + +- 72754db: **BREAKING**: All types of route refs are always considered optional by `useRouteRef`, which means the caller must always handle a potential `undefined` return value. Related to this change, the `optional` option from `createExternalRouteRef` has been removed, since it is no longer necessary. + + This is released as an immediate breaking change as we expect the usage of the new route refs to be extremely low or zero, since plugins that support the new system will still use route refs and `useRouteRef` from `@backstage/core-plugin-api` in combination with `convertLegacyRouteRef` from `@backstage/core-compat-api`. + +### Patch Changes + +- 6f72c2b: Fixing issue with extension blueprints `inputs` merging. + +- 210d066: Added support for using the `params` in other properties of the `createExtensionBlueprint` options by providing a callback. + +- 9b356dc: Renamed `createPlugin` to `createFrontendPlugin`. The old symbol is still exported but deprecated. + +- a376559: Correct the `TConfig` type of data references to only contain config + +- 4e53ad6: Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint` + + This allows the creation of extension instances with the following pattern: + + ```tsx + // create the extension blueprint which is used to create instances + const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'test', input: 'default' }, + output: [coreExtensionData.reactElement], + factory(params: { text: string }) { + return [coreExtensionData.reactElement(

    {params.text}

    )]; + }, + }); + + // create an instance of the extension blueprint with params + const testExtension = EntityCardBlueprint.make({ + name: 'foo', + params: { + text: 'Hello World', + }, + }); + ``` + +- 9b89b82: The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path. + +- e493020: Deprecated `inputs` and `configSchema` options for `createComponentExtenion`, these will be removed in a future release + +- 7777b5f: Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons. + + ```tsx + import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; + + const exampleIconBundle = IconBundleBlueprint.make({ + name: 'example-bundle', + params: { + icons: { + user: MyOwnUserIcon, + }, + }, + }); + ``` + +- 99abb6b: Support overriding of plugin extensions using the new `plugin.withOverrides` method. + + ```tsx + import homePlugin from '@backstage/plugin-home'; + + export default homePlugin.withOverrides({ + extensions: [ + homePage.getExtension('page:home').override({ + *factory(originalFactory) { + yield* originalFactory(); + yield coreExtensionData.reactElement(

    My custom home page

    ); + }, + }), + ], + }); + ``` + +- 813cac4: Add an `ExtensionBoundary.lazy` function to create properly wrapped lazy-loading enabled elements, suitable for use with `coreExtensionData.reactElement`. The page blueprint now automatically leverages this. + +- a65cfc8: Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint. + +- 3be9aeb: Extensions have been changed to be declared with an array of inputs and outputs, rather than a map of named data refs. This change was made to reduce confusion around the role of the input and output names, as well as enable more powerful APIs for overriding extensions. + + An extension that was previously declared like this: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( +
    + Example + {inputs.items.map(item => { + return
    {item.output.element}
    ; + })} +
    + ), + }; + }, + }); + ``` + + Should be migrated to the following: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput([coreExtensionData.reactElement]), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( +
    + Example + {inputs.items.map(item => { + return
    {item.get(coreExtensionData.reactElement)}
    ; + })} +
    , + ), + ]; + }, + }); + ``` + +- 34f1b2a: Support merging of `inputs` in extension blueprints, but stop merging `output`. In addition, the original factory in extension blueprints now returns a data container that both provides access to the returned data, but can also be forwarded as output. + +- 3fb421d: Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. + +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- 31bfc44: Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release. + + For example, the following code: + + ```ts + export const myExtension = + createExtensionDataRef('my-plugin.my-data'); + ``` + + Should be updated to the following: + + ```ts + export const myExtension = createExtensionDataRef().with({ + id: 'my-plugin.my-data', + }); + ``` + +- 6349099: Added config input type to the extensions + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/integration@1.14.0 + +### Minor Changes + +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. + +### Patch Changes + +- c591670: Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.2.0 + +### Minor Changes + +- 75d026a: Support for Cloudflare Custom Headers and Custom Cookie Auth Name + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-node@0.5.0 + +### Minor Changes + +- 579afd0: **BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog@1.22.0 + +### Minor Changes + +- 6925dcb: Introduces the HasSubdomainsCard component that displays the subdomains of a given domain + +### Patch Changes + +- 496b8a9: Export `RelatedEntitiesCard` presets to be reused. +- 604a504: The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend@1.25.0 + +### Minor Changes + +- 163ba08: Deprecated `RouterOptions`, `CatalogBuilder`, and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 776eb56: `ProcessorOutputCollector` returns an error when receiving deferred entities that have an invalid `metadata.annotations` format. + + This allows to return an error on an actual validation issue instead of reporting that the location annotations are missing afterwards, which is misleading for the users. + +- 389f5a4: Update deprecated url-reader-related imports. + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint + +- 51240ee: Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-aws@0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- 9342ac8: Removed unused dependency +- 389f5a4: Update deprecated url-reader-related imports. +- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-azure@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-gcp@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-github@0.7.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- c1eb809: Fix GitHub `repository` event support. + + - `$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. + - `$.repository.url` is not always returning the expected and required value. Use `$.repository.html_url` instead. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-github-org@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend-module-github@0.7.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-gitlab@0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- c7b14ed: Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.1.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.5.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-ldap@0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-msgraph@0.6.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-notifications@0.3.0 + +### Minor Changes + +- 0410fc9: By default, set notification as read when opening snackbar or web notification link + +### Patch Changes + +- 80b84f7: Fixed issue with notification reloading on page change +- b58e452: Broadcast notifications are now decorated with an icon. +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-notifications-backend-module-email@0.2.0 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

    ${notification.content}

    `; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- cdb630d: Add support for stream transport for debugging purposes +- 83faf24: Notification email processor supports allowing or denying specific email addresses from receiving notifications +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-scaffolder@1.24.0 + +### Minor Changes + +- 1552c33: Changed the way to display entities in `MyGroupsPicker` to use `entityPresentationApi` and make it consistent across scaffolder pickers +- 3fca643: Added field extension `RepoBranchPicker` that supports autocompletion for Bitbucket + +### Patch Changes + +- 47ed51b: Add an extra bit of height to the EntityPicker dropdown to make it clear there are more options to select from, and to remove the scroll bar when there is less than 10 options +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 2ae63cd: add i18n for scaffolder +- d18f4eb: Fix undefined in the title of Scaffolder Runs on the page load +- 896a22d: Fix helper text margin for scaffolder EntityNamePicker and EntityTagsPicker when using outlined text field +- bbd9f56: Cleaned up codebase of RepoUrlPicker +- b8600fe: Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-backend@1.24.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- dcd6a79: Added OpenTelemetry support to Scaffolder metrics + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- b63d378: Update internal imports +- ef87e06: Fix scaffolder action `catalog:write` to write to directories that don't already exist +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-react@1.11.0 + +### Minor Changes + +- 8839381: Add scaffolder option to display object items in separate rows on review page + +### Patch Changes + +- 072c00c: Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- d0e95a7: Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps` +- 4670f06: support `ajv-errors` for scaffolder validation to allow for customizing the error messages +- 04759f2: Fix null check in `isJsonObject` utility function for scaffolder review state component +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-search-backend-module-catalog@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-search-backend-module-explore@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 93fc1a0: Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.4`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-techdocs@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-search-backend-node@1.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 3123c16: Fix package metadata +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-techdocs-common@0.1.0 + +### Minor Changes + +- 4698e1f: Initial release of the techdocs-common package. + +## @backstage/app-defaults@1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/backend-defaults@0.4.2 + +### Patch Changes + +- 0d16b52: Add access restrictions to the JWKS external access method config schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3b429fb: Added deprecation warning to urge users to perform the auth service migration or implement their own token manager service. + See for more information. +- 7681b17: update the `morgan` middleware to use a custom format to prevent PII from being logged +- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. +- 8e967da: Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`. +- 7c5f3b0: Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. +- 81f930a: use formatted query to prevent chance of SQL-injection +- 1d5f298: Avoid excessive numbers of error listeners on cache clients +- Updated dependencies + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/backend-dev-utils@0.1.5 + +### Patch Changes + +- 3a35172: Fix `EventEmitter` memory leak in the development utilities + +## @backstage/backend-openapi-utils@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/errors@1.2.4 + +## @backstage/catalog-client@1.6.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/core-app-api@1.14.2 + +### Patch Changes + +- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-compat-api@0.2.8 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 16cf96c: Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. +- 519b8e0: Added new utilities for converting legacy plugins and extensions to the new system. The `convertLegacyPlugin` option will convert an existing plugin to the new system, although you need to supply extensions for the plugin yourself. To help out with this, there is also a new `convertLegacyPageExtension` which converts an existing page extension to the new system. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-components@0.14.10 + +### Patch Changes + +- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.18 + +### Patch Changes + +- c0a705d: Added the Kubernetes plugin to `create-app` +- d7a0aa3: Bumped create-app version. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6c1081c: Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data +- bfeba46: Included permission config and enabled it out of the box +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## @backstage/frontend-test-utils@0.1.12 + +### Patch Changes + +- 8209449: Added new APIs for testing extensions + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. + +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. + +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. + +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- c00e1a0: Deprecate the `.render` method of the `createExtensionTester` in favour of using `renderInTestApp` directly. + + ```tsx + import { + renderInTestApp, + createExtensionTester, + } from '@backstage/frontend-test-utils'; + + const tester = createExtensionTester(extension); + + const { getByTestId } = renderInTestApp(tester.reactElement()); + + // or if you're not using `coreExtensionData.reactElement` as the output ref + const { getByTestId } = renderInTestApp(tester.get(myComponentRef)); + ``` + +- 264e10f: Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. + +- 264e10f: Refactor `.make` method on Blueprints into two different methods, `.make` and `.makeWithOverrides`. + + When using `createExtensionBlueprint` you can define parameters for the factory function, if you wish to take advantage of these parameters you should use `.make` when creating an extension instance of a Blueprint. If you wish to override more things other than the standard `attachTo`, `name`, `namespace` then you should use `.makeWithOverrides` instead. + + `.make` is reserved for simple creation of extension instances from Blueprints using higher level parameters, whereas `.makeWithOverrides` is lower level and you have more control over the final extension. + +- 6349099: Added config input type to the extensions + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/frontend-app-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.5.10 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/repo-tools@0.9.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-app-api@1.14.2 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-api-docs@0.11.8 + +### Patch Changes + +- 770ba02: `ConsumingComponentsCard` and `ProvidingComponentsCard` will now optionally accept `columns` to override which table columns are displayed +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- ebfeb40: Added `resolvers` prop to `AsyncApiDefinitionWidget`. This allows to override the default http/https resolvers, for example to add authentication to requests to internal schema registries. +- 4b6d2cb: Updated dependency `@graphiql/react` to `^0.23.0`. +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app-backend@0.3.72 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6bd6fda: Deprecate `createRouter` and its options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23 + +## @backstage/plugin-app-node@0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + +## @backstage/plugin-app-visualizer@0.1.9 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- e493020: Fixing issue with the visualizer crashing when clicking on the detailed view because of `routeRef` parameters +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.10 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- cc9a7a5: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.2.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema + +- 4ea354f: Added a `signer` configuration option to validate against the token claims. We strongly recommend that you set this value (typically on the format `arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456`) to ensure that the auth provider can safely check the authenticity of any incoming tokens. + + Example: + + ```diff + auth: + providers: + awsalb: + # this is the URL of the IdP you configured + issuer: 'https://example.okta.com/oauth2/default' + # this is the ARN of your ALB instance + + signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + # this is the region where your ALB instance resides + region: 'us-west-2' + signIn: + resolvers: + # typically you would pick one of these + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + ``` + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 39f36a9: Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.16 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-auth-react@0.1.5 + +### Patch Changes + +- aeac3e9: feat: Hide visibility of CookieAuthRedirect +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-bitbucket-cloud-common@0.2.22 + +### Patch Changes + +- 3fca643: Added method `listBranchesByRepository` to `BitbucketCloudClient` +- Updated dependencies + - @backstage/integration@1.14.0 + +## @backstage/plugin-catalog-backend-module-logs@0.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.41 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + +## @backstage/plugin-catalog-common@1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-catalog-graph@0.4.8 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws +- 4a529c2: Use `entityPresentationApi` for the node title and the icon. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.2 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-node@1.12.5 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-react@1.12.3 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 7ca331c: Correct `EntityDisplayName`'s icon alignment with the text. +- 9b89b82: Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export. +- bebd569: Fix extra divider displayed on user list picker component +- 519b8e0: Added utilities for converting existing entity card and content extensions to the new frontend system. This is in particular useful when used in combination with the new `convertLegacyPlugin` utility from `@backstage/core-compat-api`. +- d001a42: Fix label related accessibility issues with `FavorityEntity` +- 012e3eb: Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-config-schema@0.1.58 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.17 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-devtools-backend@0.3.9 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 32a38e1: Removed unused code for lockfile analysis. +- 2886ef7: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + +## @backstage/plugin-devtools-common@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-azure@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-gerrit@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-github@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-gitlab@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-test-utils@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-node@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + +## @backstage/plugin-home@0.7.9 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- fdcc059: Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16 + +## @backstage/plugin-home-react@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.13 + +### Patch Changes + +- e6c15cc: Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-backend@0.18.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- 8c1aa06: Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. + This was already documented and supported by the plugin. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-kubernetes-node@0.1.17 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-cluster@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-common@0.8.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.17 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-react@0.4.2 + +### Patch Changes + +- 954a593: `Liveness Probe` added in ContainerCard Component of PodDrawer +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-notifications-backend@0.3.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- 8013044: fix: consider broadcast union with user +- 7a05f50: Allow using notifications without users in the catalog +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + +## @backstage/plugin-notifications-node@0.2.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + +## @backstage/plugin-org@0.6.28 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-org-react@0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.47 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + +## @backstage/plugin-permission-common@0.8.1 + +### Patch Changes + +- df784fe: Add the MetadataResponse type from @backstage/plugin-permission-node, since this + type might be used in frontend code. +- 137fa34: Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-permission-node@0.8.1 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-react@0.4.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-proxy-backend@0.5.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.15 + +### Patch Changes + +- 187f583: Added examples for publish:azure action and updated its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3fca643: Added autocompletion support for resource `branches` +- d57967c: Add ability to set the initial commit message when initializing a repository using the scaffolder action. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- e3b64be: Added examples for publish:bitbucketServer action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 24de005: Added test cases for publish:gitea examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.1 + +### Patch Changes + +- d21d307: Added examples for github:environment:create action and improve its test cases +- 6d4cb97: Added examples for github:repo:create action and improved test cases +- cd203f1: Added examples for action github:pages and improved its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + +### Patch Changes + +- da97131: Added test cases for gitlab:issues:create examples + +- fad1b90: Allow the `createGitlabProjectVariableAction` to use oauth tokens + +- aab708e: Added test cases for gitlab:issue:edit examples + +- ef742dc: Added test cases for gitlab:projectAccessToken:create example + +- 1ba4c2f: Added test cases for gitlab:pipeline:trigger examples + +- a6603e4: Add custom action for merge request: **auto** + + The **Auto** action selects the committed action between _create_ and _update_. + + The **Auto** action fetches files using the **/projects/repository/tree endpoint**. + After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6 + +### Patch Changes + +- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.40 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 449def7: Add examples for fetch:rails scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 382e868: Added test cases for sentry:project:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10 + +## @backstage/plugin-scaffolder-common@1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.24.0 + - @backstage/backend-test-utils@0.5.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + +## @backstage/plugin-search@1.4.15 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 3123c16: Fix package metadata +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-search-backend@1.5.15 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-search-backend-module-pg@0.5.33 + +### Patch Changes + +- 7251567: Removing `@backstage/backend-app-api` dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-common@1.2.14 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.14 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 3123c16: Fix package metadata +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-search-common@1.2.14 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-signals@0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-signals-backend@0.1.9 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-signals-node@0.1.9 + +## @backstage/plugin-signals-node@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-techdocs@1.10.8 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 27794d1: Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. +- 4490d73: Refactor TechDocs' mkdocs-redirects support. +- 8543e72: TechDocs redirect feature now includes a notification to the user before they are redirected. +- 67e76f2: TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. +- bdc5471: Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.5.10 + - @backstage/plugin-techdocs-react@1.2.7 + +## @backstage/plugin-techdocs-backend@1.10.10 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b77fbf4: Added back `type: 'local'` to TechDocs config schema for `publisher` +- a16632c: Update configuration schema to match actual behavior +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/integration@1.14.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-techdocs-react@1.2.7 + +## @backstage/plugin-techdocs-node@1.12.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 69bd940: Use annotation constants from new techdocs-common package. +- 949083d: Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently. +- 5cedd9f: Fix TechDocs Edit URL for nested docs +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-techdocs-react@1.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.11 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-node@0.1.9 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-devtools@0.1.17 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## example-app-next@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-app-visualizer@0.1.9 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## app-next-example-plugin@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + +## example-backend@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-notifications-backend@0.3.4 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.3.0 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20 + +## example-backend-legacy@0.2.101 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-search-backend-module-pg@0.5.33 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4 + - @backstage/plugin-signals-node@0.1.9 + +## e2e-test@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.5.10 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + +## @internal/plugin-todo-list@1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @internal/plugin-todo-list-backend@1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## @internal/plugin-todo-list-common@1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 diff --git a/package.json b/package.json index 285d3ad3f5..740f7a412f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.30.0-next.4", + "version": "1.30.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 410841fec9..72c172e395 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + ## 1.5.10-next.2 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 284e832ae5..ee21affccf 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.10-next.2", + "version": "1.5.10", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 58762ab5e5..af5b4a5c24 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # app-next-example-plugin +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + ## 0.0.14-next.3 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index e3ddfe32dd..5bb8318e2b 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-next-example-plugin", - "version": "0.0.14-next.3", + "version": "0.0.14", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index f37f968fed..5d3a1d5a3c 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,48 @@ # example-app-next +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-app-visualizer@0.1.9 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + ## 0.0.14-next.4 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index e8905091bb..fa37cce5c8 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.14-next.4", + "version": "0.0.14", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index c80450daa9..f9cebb78e6 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,46 @@ # example-app +## 0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-devtools@0.1.17 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + ## 0.2.100-next.4 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 14fc3a0ae6..c27540f63e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.100-next.4", + "version": "0.2.100", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 5d8507cbd2..076892c3b1 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/backend-app-api +## 0.9.0 + +### Minor Changes + +- da4fde5: **BREAKING**: Removed several deprecated service factories. These can instead be imported from `@backstage/backend-defaults` package. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- 389f5a4: Remove deprecated `urlReaderServiceFactory`, please import from `@backstage/backend-defaults/urlReader` instead. + +### Patch Changes + +- 8b13183: Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 7c5f3b0: Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. +- 80a0737: Added configuration for the `packages` options to config schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.8.1-next.3 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index e5a80cc1eb..27cbb0b916 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-app-api", - "version": "0.8.1-next.3", + "version": "0.9.0", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 9907005ba9..40ba8fb687 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,72 @@ # @backstage/backend-common +## 0.24.0 + +### Minor Changes + +- 389f5a4: **BREAKING**: Removed the following `Url Reader` deprecated exports: + + - UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; + - AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketCloudUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketServerUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GithubUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GitlabUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GerritUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GiteaUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - HarnessUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - AwsS3UrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - FetchUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaders: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReadersOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaderPredicateTuple: Import from `@backstage/backend-defaults/urlReader` instead; + - FromReadableArrayOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReaderFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlOptions:Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlResponseFactoryFromStreamOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeResponseFactoryOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead. + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6795e33: This package is marked as `deprecated` and will be removed in a near future, please follow the deprecated instructions for the exports you still use. +- 7e13b7a: The remaining exports in the package have now been deprecated: + + - `cacheToPluginCacheManager` + - `createLegacyAuthAdapters` + - `LegacyCreateRouter` + - `legacyPlugin` + - `loggerToWinstonLogger` + - `makeLegacyPlugin` + + Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating). + +- ddde5fe: Internal type refactor. +- b63d378: export `createConfigSecretEnumerator` from `@backstage/backend-common` instead of `@backstage/backend-app-api`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + ## 0.23.4-next.3 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index c088e32e4d..ce26958aaf 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.23.4-next.3", + "version": "0.24.0", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index eaf4165241..64d9b77fba 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/backend-defaults +## 0.4.2 + +### Patch Changes + +- 0d16b52: Add access restrictions to the JWKS external access method config schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3b429fb: Added deprecation warning to urge users to perform the auth service migration or implement their own token manager service. + See https://backstage.io/docs/tutorials/auth-service-migration for more information. +- 7681b17: update the `morgan` middleware to use a custom format to prevent PII from being logged +- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. +- 8e967da: Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`. +- 7c5f3b0: Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. +- 81f930a: use formatted query to prevent chance of SQL-injection +- 1d5f298: Avoid excessive numbers of error listeners on cache clients +- Updated dependencies + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + ## 0.4.2-next.3 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 0da8bb9d91..3280a3cf9d 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.4.2-next.3", + "version": "0.4.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-dev-utils/CHANGELOG.md b/packages/backend-dev-utils/CHANGELOG.md index d60b686e79..81686fb493 100644 --- a/packages/backend-dev-utils/CHANGELOG.md +++ b/packages/backend-dev-utils/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/backend-dev-utils +## 0.1.5 + +### Patch Changes + +- 3a35172: Fix `EventEmitter` memory leak in the development utilities + ## 0.1.4 ### Patch Changes diff --git a/packages/backend-dev-utils/package.json b/packages/backend-dev-utils/package.json index b24c9102b2..4d22c5010f 100644 --- a/packages/backend-dev-utils/package.json +++ b/packages/backend-dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dev-utils", - "version": "0.1.4", + "version": "0.1.5", "backstage": { "role": "node-library" }, diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index b8e0f5aef8..5828cebe32 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/backend-dynamic-feature-service +## 0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.16-next.3 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index b977fe5756..ac06fcd8d0 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dynamic-feature-service", - "version": "0.2.16-next.3", + "version": "0.3.0", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index ab180a8062..3b6a1af6f4 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,47 @@ # example-backend-legacy +## 0.2.101 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-search-backend-module-pg@0.5.33 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4 + - @backstage/plugin-signals-node@0.1.9 + ## 0.2.101-next.3 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 446207959b..0bdfa350e9 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.101-next.3", + "version": "0.2.101", "backstage": { "role": "backend" }, diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index f7a238f712..0d58c6ad9c 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-openapi-utils +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/errors@1.2.4 + ## 0.1.16-next.3 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 6125fa82ee..a64ef674a9 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-openapi-utils", "description": "OpenAPI typescript support.", - "version": "0.1.16-next.3", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 6f4ed70318..cb830feb33 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/backend-plugin-api +## 0.8.0 + +### Minor Changes + +- 389f5a4: **BREAKING** Deleted the following deprecated `UrlReader` exports + + - ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` instead. + +- 7c5f3b0: The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. + + We're looking for ways to make it possible to augment services without the need to replace the entire service. + + Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: + + ```diff + // @backstage/backend-defaults + + + export const urlReaderFactoriesServiceRef = createServiceRef({ + + id: 'core.urlReader.factories', + + scope: 'plugin', + + multiton: true, + + }); + + ... + + export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + + factories: urlReaderFactoriesServiceRef, + }, + - async factory({ config, logger }) { + + async factory({ config, logger, factories }) { + return UrlReaders.default({ + config, + logger, + + factories, + }); + }, + }); + ``` + + With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: + + ```ts + // packages/backend/index.ts + import { createServiceFactory } from '@backstage/backend-plugin-api'; + import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; + ... + + backend.add(createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, + })); + + ... + + ``` + +- c99c620: **BREAKING** Removed the following deprecated types: + + - `ServiceRefConfig` use `ServiceRefOptions` + - `RootServiceFactoryConfig` use `RootServiceFactoryOptions` + - `PluginServiceFactoryConfig` use `PluginServiceFactoryOptions` + +### Patch Changes + +- 6061061: Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 8b13183: Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. + + A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. + + Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. + + ```ts + const searchLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + *loader({ config }) { + // Example of a custom config flag to enable search + if (config.getOptionalString('customFeatureToggle.search')) { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + } + }, + }); + ``` + +- ddde5fe: Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. +- f011d1b: fix typo in `getPluginRequestToken` comments +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.8.0-next.3 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index a4a97faaa9..94793e1e49 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "0.8.0-next.3", + "version": "0.8.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index d4c9c3a59c..fbdbff1a8a 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/backend-tasks +## 0.6.0 + +### Minor Changes + +- fc24d9e: This package is deprecated and will be removed in a near future, follow the instructions below to stop using it: + + - `TaskScheduler`: Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults; + - `TaskRunner`: Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead; + - `TaskFunction`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskDescriptor`: Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead; + - `TaskInvocationDefinition`: Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinition`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinitionConfig`: Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead; + - `PluginTaskScheduler`: Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`); + - `readTaskScheduleDefinitionFromConfig`: Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead; + - `HumanDuration`: Import `TypesHumanDuration` from `@backstage/types` instead. + +### Patch Changes + +- ba9abf4: The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.5.28-next.3 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 71659e5a6f..f0209c1fa9 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-tasks", - "version": "0.5.28-next.3", + "version": "0.6.0", "description": "Common distributed task management library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 39d176e8a9..59542fb241 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/backend-test-utils +## 0.5.0 + +### Minor Changes + +- 861f162: **BREAKING**: Removed these deprecated helpers: + + - `setupRequestMockHandlers` Use `CreateMockDirectoryOptions` instead. + - `CreateMockDirectoryOptions` Use `registerMswTestHooks` instead. + + Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. + + Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- b63d378: Update internal imports +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + ## 0.4.5-next.3 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 5373c62794..682ec3067e 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "0.4.5-next.3", + "version": "0.5.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index c2e4b76dab..8c856f53ca 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,41 @@ # example-backend +## 0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-notifications-backend@0.3.4 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.3.0 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20 + ## 0.0.29-next.3 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 6d4a99abbb..570a545741 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.29-next.3", + "version": "0.0.29", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 361722fbd1..9ed8a50b91 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 1.6.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + ## 1.6.6-next.0 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 221e25c668..29c1d3049c 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.6.6-next.0", + "version": "1.6.6", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 360a0b927e..1242c48f31 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/catalog-model +## 1.6.0 + +### Minor Changes + +- 34fa803: Introduce an optional spec.type attribute on the Domain and System entity kinds + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.6.0-next.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index e4224cdff9..150317aee1 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "1.6.0-next.0", + "version": "1.6.0", "description": "Types and validators that help describe the model of a Backstage Catalog", "backstage": { "role": "common-library" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index e8092c5aae..ec823ef23e 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/cli +## 0.27.0 + +### Minor Changes + +- 32a38e1: **BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. + + The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files. + + The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic. + + The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches. + + The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future. + +### Patch Changes + +- 7eb08a6: Add frontend-dynamic-container role to eslint config factory +- b2d97fd: Fixing loading of additional config files with new `ConfigSources` +- fbc7819: Use ES2022 in CLI bundler +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. +- ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag. +- 239dffc: Remove usage of deprecated functionality from @backstage/config-loader +- e6e7d86: Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. +- 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` +- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. +- adabb40: New command now supports setting package license +- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point. +- Updated dependencies + - @backstage/config-loader@1.9.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + ## 0.27.0-next.4 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index de38b0c656..c3f70bcbf4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.27.0-next.4", + "version": "0.27.0", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 78f2123970..a60e86a6c8 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/config-loader +## 1.9.0 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and + `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource + for the default app-config.yaml configuration file if it's not present. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.9.0-next.2 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index a47a17c12f..98d233b348 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.9.0-next.2", + "version": "1.9.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 26b9eb20fe..2ae26f8b97 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-app-api +## 1.14.2 + +### Patch Changes + +- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 1.14.1-next.0 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 6a107e0a87..ad8b4b49c0 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.14.2-next.0", + "version": "1.14.2", "publishConfig": { "access": "public" }, diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index d5570ef632..35849d453c 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/core-compat-api +## 0.2.8 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 16cf96c: Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. +- 519b8e0: Added new utilities for converting legacy plugins and extensions to the new system. The `convertLegacyPlugin` option will convert an existing plugin to the new system, although you need to supply extensions for the plugin yourself. To help out with this, there is also a new `convertLegacyPageExtension` which converts an existing page extension to the new system. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + ## 0.2.8-next.3 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 9ebc1a0185..5af7d1b960 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.2.8-next.3", + "version": "0.2.8", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index a2463f594e..f50b830702 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/core-components +## 0.14.10 + +### Patch Changes + +- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + ## 0.14.10-next.0 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index a2688a0037..154331d70d 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.14.10-next.0", + "version": "0.14.10", "publishConfig": { "access": "public" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 29ebe72296..9a21164f9b 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/create-app +## 0.5.18 + +### Patch Changes + +- c0a705d: Added the Kubernetes plugin to `create-app` +- d7a0aa3: Bumped create-app version. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6c1081c: Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data +- bfeba46: Included permission config and enabled it out of the box +- Updated dependencies + - @backstage/cli-common@0.1.14 + ## 0.5.18-next.4 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 76934282ca..48a3651f71 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.18-next.4", + "version": "0.5.18", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index a5e1688e6d..6d40e8fe18 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/dev-utils +## 1.0.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + ## 1.0.37-next.3 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 255cc478b0..447cc13cdc 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.37-next.3", + "version": "1.0.37", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 23337c9ca3..13f01f7458 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,14 @@ # e2e-test +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + ## 0.2.19-next.4 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 0e11195ba1..2e2ca6e331 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.19-next.4", + "version": "0.2.19", "private": true, "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index f26e52bccd..961d099fa1 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/frontend-app-api +## 0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 7777b5f: Support icon overriding with the new `IconBundleBlueprint` API. +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.7.5-next.3 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 4c82731c4c..3f13b4ad93 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.7.5-next.3", + "version": "0.8.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index d419a0a5cb..cce56f0855 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,189 @@ # @backstage/frontend-plugin-api +## 0.7.0 + +### Minor Changes + +- 72754db: **BREAKING**: All types of route refs are always considered optional by `useRouteRef`, which means the caller must always handle a potential `undefined` return value. Related to this change, the `optional` option from `createExternalRouteRef` has been removed, since it is no longer necessary. + + This is released as an immediate breaking change as we expect the usage of the new route refs to be extremely low or zero, since plugins that support the new system will still use route refs and `useRouteRef` from `@backstage/core-plugin-api` in combination with `convertLegacyRouteRef` from `@backstage/core-compat-api`. + +### Patch Changes + +- 6f72c2b: Fixing issue with extension blueprints `inputs` merging. +- 210d066: Added support for using the `params` in other properties of the `createExtensionBlueprint` options by providing a callback. +- 9b356dc: Renamed `createPlugin` to `createFrontendPlugin`. The old symbol is still exported but deprecated. +- a376559: Correct the `TConfig` type of data references to only contain config +- 4e53ad6: Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint` + + This allows the creation of extension instances with the following pattern: + + ```tsx + // create the extension blueprint which is used to create instances + const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'test', input: 'default' }, + output: [coreExtensionData.reactElement], + factory(params: { text: string }) { + return [coreExtensionData.reactElement(

    {params.text}

    )]; + }, + }); + + // create an instance of the extension blueprint with params + const testExtension = EntityCardBlueprint.make({ + name: 'foo', + params: { + text: 'Hello World', + }, + }); + ``` + +- 9b89b82: The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path. +- e493020: Deprecated `inputs` and `configSchema` options for `createComponentExtenion`, these will be removed in a future release +- 7777b5f: Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons. + + ```tsx + import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; + + const exampleIconBundle = IconBundleBlueprint.make({ + name: 'example-bundle', + params: { + icons: { + user: MyOwnUserIcon, + }, + }, + }); + ``` + +- 99abb6b: Support overriding of plugin extensions using the new `plugin.withOverrides` method. + + ```tsx + import homePlugin from '@backstage/plugin-home'; + + export default homePlugin.withOverrides({ + extensions: [ + homePage.getExtension('page:home').override({ + *factory(originalFactory) { + yield* originalFactory(); + yield coreExtensionData.reactElement(

    My custom home page

    ); + }, + }), + ], + }); + ``` + +- 813cac4: Add an `ExtensionBoundary.lazy` function to create properly wrapped lazy-loading enabled elements, suitable for use with `coreExtensionData.reactElement`. The page blueprint now automatically leverages this. +- a65cfc8: Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint. +- 3be9aeb: Extensions have been changed to be declared with an array of inputs and outputs, rather than a map of named data refs. This change was made to reduce confusion around the role of the input and output names, as well as enable more powerful APIs for overriding extensions. + + An extension that was previously declared like this: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( +
    + Example + {inputs.items.map(item => { + return
    {item.output.element}
    ; + })} +
    + ), + }; + }, + }); + ``` + + Should be migrated to the following: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput([coreExtensionData.reactElement]), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( +
    + Example + {inputs.items.map(item => { + return
    {item.get(coreExtensionData.reactElement)}
    ; + })} +
    , + ), + ]; + }, + }); + ``` + +- 34f1b2a: Support merging of `inputs` in extension blueprints, but stop merging `output`. In addition, the original factory in extension blueprints now returns a data container that both provides access to the returned data, but can also be forwarded as output. +- 3fb421d: Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- 31bfc44: Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release. + + For example, the following code: + + ```ts + export const myExtension = + createExtensionDataRef('my-plugin.my-data'); + ``` + + Should be updated to the following: + + ```ts + export const myExtension = createExtensionDataRef().with({ + id: 'my-plugin.my-data', + }); + ``` + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.7.0-next.3 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 65575bad9c..c76bae094e 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.7.0-next.3", + "version": "0.7.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 5c8626f7a7..a35ec8203b 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/frontend-test-utils +## 0.1.12 + +### Patch Changes + +- 8209449: Added new APIs for testing extensions +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- c00e1a0: Deprecate the `.render` method of the `createExtensionTester` in favour of using `renderInTestApp` directly. + + ```tsx + import { + renderInTestApp, + createExtensionTester, + } from '@backstage/frontend-test-utils'; + + const tester = createExtensionTester(extension); + + const { getByTestId } = renderInTestApp(tester.reactElement()); + + // or if you're not using `coreExtensionData.reactElement` as the output ref + const { getByTestId } = renderInTestApp(tester.get(myComponentRef)); + ``` + +- 264e10f: Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. +- 264e10f: Refactor `.make` method on Blueprints into two different methods, `.make` and `.makeWithOverrides`. + + When using `createExtensionBlueprint` you can define parameters for the factory function, if you wish to take advantage of these parameters you should use `.make` when creating an extension instance of a Blueprint. If you wish to override more things other than the standard `attachTo`, `name`, `namespace` then you should use `.makeWithOverrides` instead. + + `.make` is reserved for simple creation of extension instances from Blueprints using higher level parameters, whereas `.makeWithOverrides` is lower level and you have more control over the final extension. + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/frontend-app-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.5.10 + - @backstage/types@1.1.1 + ## 0.1.12-next.3 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 80e8a1c4ac..526e8b1713 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.1.12-next.3", + "version": "0.1.12", "backstage": { "role": "web-library" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 36ecf6f718..a5bf99ba4d 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/integration-react +## 1.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + ## 1.1.30-next.0 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index de42eb2798..0ae77a9bd2 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.1.30-next.0", + "version": "1.1.30", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index b49c3410ec..9e8fef7af8 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/integration +## 1.14.0 + +### Minor Changes + +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. + +### Patch Changes + +- c591670: Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 1.14.0-next.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 0d1b733bbf..83c6e4ce8b 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.14.0-next.0", + "version": "1.14.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index f429daeb02..ffb51ea7a7 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/repo-tools +## 0.9.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/errors@1.2.4 + ## 0.9.5-next.3 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index c18d2c62d9..f337329537 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.5-next.3", + "version": "0.9.5", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index ec4a5cd46f..3d0673f221 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.99 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.5.10 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + ## 0.2.99-next.4 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index ece31d31f3..64774310f8 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.99-next.4", + "version": "0.2.99", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index f31cd879e1..18b7b5150d 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @techdocs/cli +## 1.8.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + ## 1.8.17-next.3 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index c468a1a30c..b14369cfb9 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.17-next.3", + "version": "1.8.17", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index a6c013a34a..ef2aa614e8 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/test-utils +## 1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-app-api@1.14.2 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-react@0.4.25 + ## 1.5.10-next.2 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 3007c734bd..73089ddc54 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.5.10-next.2", + "version": "1.5.10", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 5050211099..211f073321 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-api-docs +## 0.11.8 + +### Patch Changes + +- 770ba02: `ConsumingComponentsCard` and `ProvidingComponentsCard` will now optionally accept `columns` to override which table columns are displayed +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- ebfeb40: Added `resolvers` prop to `AsyncApiDefinitionWidget`. This allows to override the default http/https resolvers, for example to add authentication to requests to internal schema registries. +- 4b6d2cb: Updated dependency `@graphiql/react` to `^0.23.0`. +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + ## 0.11.8-next.3 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index b0f045cade..7564b858a9 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.8-next.3", + "version": "0.11.8", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index d4ac37c95c..604ef6f305 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-app-backend +## 0.3.72 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6bd6fda: Deprecate `createRouter` and its options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23 + ## 0.3.72-next.3 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 016ecd079d..b7fbef8b46 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.72-next.3", + "version": "0.3.72", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 009209d362..607aa68750 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-node +## 0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + ## 0.1.23-next.3 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index 972cbe1a87..4e28a853ed 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-node", - "version": "0.1.23-next.3", + "version": "0.1.23", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index f850656652..f7dad4bb44 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-app-visualizer +## 0.1.9 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- e493020: Fixing issue with the visualizer crashing when clicking on the detailed view because of `routeRef` parameters +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + ## 0.1.9-next.3 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 4efcf0faf0..7ee42ef335 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.9-next.3", + "version": "0.1.9", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 5c99e93a17..779a96f093 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.2.4-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index e35b322c34..4be0473a17 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.2.4-next.3", + "version": "0.2.4", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index d100c055de..6f2dec2347 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.15 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 4ea354f: Added a `signer` configuration option to validate against the token claims. We strongly recommend that you set this value (typically on the format `arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456`) to ensure that the auth provider can safely check the authenticity of any incoming tokens. + + Example: + + ```diff + auth: + providers: + awsalb: + # this is the URL of the IdP you configured + issuer: 'https://example.okta.com/oauth2/default' + # this is the ARN of your ALB instance + + signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + # this is the region where your ALB instance resides + region: 'us-west-2' + signIn: + resolvers: + # typically you would pick one of these + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + ``` + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + ## 0.1.15-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index c012a4006d..7b3498ceb2 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.1.15-next.3", + "version": "0.1.15", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index a118b1c4c5..3961bab52a 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + ## 0.1.6-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 65173f3e63..df83acff7b 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.1.6-next.3", + "version": "0.1.6", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index 7fabd638bd..ff2c8725be 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.1.6 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.1.6-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index b3747e3892..ed8cfa981a 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.1.6-next.3", + "version": "0.1.6", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 33c617c996..51f1ce5b17 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.2.0 + +### Minor Changes + +- 75d026a: Support for Cloudflare Custom Headers and Custom Cookie Auth Name + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.2.0-next.3 ### Minor Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index 69a59cc8f5..646bf720a2 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.2.0-next.3", + "version": "0.2.0", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index b36137aca8..4099ad1c09 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.2.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.18-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 33fb69e9aa..41c8ccc2b8 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.2.18-next.3", + "version": "0.2.18", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 1b7fba93cb..46387e4421 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.1.20-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 398fc3ddc8..5e0f0d527b 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.1.20-next.3", + "version": "0.1.20", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index f1f3e51923..0a9186b385 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.1.20-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 515d89e711..4c48fd014c 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.1.20-next.3", + "version": "0.1.20", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index b12ecd0cd2..3eb7e65ec6 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.1.20-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 9d6dc93ed3..307246514f 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.1.20-next.3", + "version": "0.1.20", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index b562085594..26dabc7b5c 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + ## 0.1.9-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index bc7e381591..d1b8caf3eb 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.1.9-next.3", + "version": "0.1.9", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index d50a8bb578..2c3e4f6d33 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.1.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 39f36a9: Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.1.18-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index d4b6585b19..8a1537771b 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.1.18-next.3", + "version": "0.1.18", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index ecceed8507..7762658a12 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.2.4-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 093d3b20c0..d37a00fccc 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.2.4-next.3", + "version": "0.2.4", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index 71ac4d4bcc..a10e533977 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + ## 0.1.16-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index 1fae82d754..2235784779 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.1.16-next.3", + "version": "0.1.16", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 7fee83d9b6..36fbf7e8c4 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + ## 0.2.4-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 535cc083f8..a90908c491 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.2.4-next.3", + "version": "0.2.4", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index fe2579b9ca..b66256f002 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.0.16 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.0.16-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 9d0d648f85..d1330f2457 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", - "version": "0.0.16-next.3", + "version": "0.0.16", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index 918a35e8b6..7302199487 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.1.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + ## 0.1.4-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index 1f0bb22c08..2e0a997e77 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.1.4-next.3", + "version": "0.1.4", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 1dc73a0cf5..79ab8db9d5 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + ## 0.1.17-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 54e4793cbe..bafbd82855 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.1.17-next.3", + "version": "0.1.17", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index fb11720c49..c7eefd4d0e 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + ## 0.2.4-next.3 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 421862aae0..7386056a3c 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.2.4-next.3", + "version": "0.2.4", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 6d1734174a..925923d2fd 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-auth-backend +## 0.22.10 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- cc9a7a5: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.2.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16 + ## 0.22.10-next.3 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 9c7b205352..8983d0983e 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.10-next.3", + "version": "0.22.10", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index a6c4f29d6f..79c783d814 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-node +## 0.5.0 + +### Minor Changes + +- 579afd0: **BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.5.0-next.3 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 3367a32b2a..781d930c21 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.5.0-next.3", + "version": "0.5.0", "backstage": { "role": "node-library", "pluginId": "auth", diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index f7d621164c..f88d6210a8 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-react +## 0.1.5 + +### Patch Changes + +- aeac3e9: feat: Hide visibility of CookieAuthRedirect +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index b68e581c74..ae534e89d9 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.5-next.0", + "version": "0.1.5", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 30e2e77a6e..3de470cfba 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.22 + +### Patch Changes + +- 3fca643: Added method `listBranchesByRepository` to `BitbucketCloudClient` +- Updated dependencies + - @backstage/integration@1.14.0 + ## 0.2.22-next.1 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index d2f22cc966..eeb5bd6ff6 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.2.22-next.1", + "version": "0.2.22", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 103b2d1670..dcad12d670 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- 9342ac8: Removed unused dependency +- 389f5a4: Update deprecated url-reader-related imports. +- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.3.18-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 7df7c3bede..2de0cf81a7 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.18-next.3", + "version": "0.4.0", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 1c193f2913..1d9e799c20 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.1.43-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index b951aa3584..186e12e8e4 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.43-next.3", + "version": "0.2.0", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 08fc9c0a39..ccee7e4572 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.2.6-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 036d4b5108..ca1e220c62 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.2.6-next.3", + "version": "0.3.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index e9b5bda880..05b0f40fb7 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.10-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 70d8a22d1c..13ee89e71c 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.2.10-next.3", + "version": "0.3.0", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 92ea2d3841..5946dace67 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.37-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index d17c970987..c53dd1a1c3 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.37-next.3", + "version": "0.2.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index c0abacbe42..7b6ca7f55e 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.1.24-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index a45c7beb34..4793e6b167 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.24-next.3", + "version": "0.2.0", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 85668dace1..74977e4c9f 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.40-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index ebbaae84ab..6b6c9d1a5c 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.40-next.3", + "version": "0.2.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 3043985aab..c130f00d2b 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend-module-github@0.7.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.1.18-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index d39105b10a..a902d80c47 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.18-next.3", + "version": "0.2.0", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 19fe2410b1..4d7a38f5d7 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-catalog-backend-module-github +## 0.7.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- c1eb809: Fix GitHub `repository` event support. + + - `$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. + - `$.repository.url` is not always returning the expected and required value. Use `$.repository.html_url` instead. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + ## 0.6.6-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index d7cdc0df07..81cc498ce5 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.6-next.3", + "version": "0.7.0", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 6b28f919c3..b6000e7a6d 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.1.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/plugin-events-node@0.3.9 + ## 0.0.6-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index b621ad5505..ba4fcd844b 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.6-next.3", + "version": "0.1.0", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 1705be9ab7..e8e40a7391 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- c7b14ed: Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + ## 0.3.22-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 623b690057..1d5000e7c5 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.3.22-next.3", + "version": "0.4.0", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 21d5777de7..b46b77b0c1 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.5.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + ## 0.4.28-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 33cfbb1745..466aca22ec 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.28-next.3", + "version": "0.5.0", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 6e9c089bef..4abf2a3d3b 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.7.1-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index b1811d23f4..abb11f8096 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.7.1-next.3", + "version": "0.8.0", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index eb3228d949..a95b43851b 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.0.2-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 7485e7be98..43afb06dde 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.0.2-next.3", + "version": "0.0.2", "description": "A module that subscribes to catalog releated events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index e0c177201b..a394664d88 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.6.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.5.31-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 4823948c2d..68e67c1cfc 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.31-next.3", + "version": "0.6.0", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index af99194b98..c24a7bb60b 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.41 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.1.41-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 5149d04b9c..4bdfe575e2 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.41-next.3", + "version": "0.1.41", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 1900a98283..0bb7df6890 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.29-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 4c77ab8168..3a12da0e66 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.29-next.3", + "version": "0.2.0", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 0bd3048063..11075a73cf 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 0.1.21-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index b61c28b370..fdb0027016 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.21-next.3", + "version": "0.1.21", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index c4c50f7c4a..0d7ab37983 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + ## 0.4.10-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 46bd85a47c..ad91bd573d 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.10-next.3", + "version": "0.4.10", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index e387fe4d94..c6347c35d3 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-catalog-backend +## 1.25.0 + +### Minor Changes + +- 163ba08: Deprecated `RouterOptions`, `CatalogBuilder`, and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 776eb56: `ProcessorOutputCollector` returns an error when receiving deferred entities that have an invalid `metadata.annotations` format. + + This allows to return an error on an actual validation issue instead of reporting that the location annotations are missing afterwards, which is misleading for the users. + +- 389f5a4: Update deprecated url-reader-related imports. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- 51240ee: Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + ## 1.24.1-next.3 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d374f63ada..4003fd2b6a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.24.1-next.3", + "version": "1.25.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 706659d062..f15f53aef4 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-common +## 1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + ## 1.0.26-next.2 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index e1ab5ec74f..b9917b9d35 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-common", - "version": "1.0.26-next.2", + "version": "1.0.26", "description": "Common functionalities for the catalog plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index c8a07dba96..a1715bbbca 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-graph +## 0.4.8 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws +- 4a529c2: Use `entityPresentationApi` for the node title and the icon. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + ## 0.4.8-next.4 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index c206df00b4..326a80fdf2 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.8-next.4", + "version": "0.4.8", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index e042262603..1b8d14ff3f 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-import +## 0.12.2 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.12.2-next.3 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 31433aa1da..7909c33936 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.12.2-next.3", + "version": "0.12.2", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 8ff814b976..1c77be04e9 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-node +## 1.12.5 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + ## 1.12.5-next.3 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 34ce2760ee..ff4c0d17a8 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.12.5-next.3", + "version": "1.12.5", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index dd7b1ae3fe..8cb8985542 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-react +## 1.12.3 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 7ca331c: Correct `EntityDisplayName`'s icon alignment with the text. +- 9b89b82: Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export. +- bebd569: Fix extra divider displayed on user list picker component +- 519b8e0: Added utilities for converting existing entity card and content extensions to the new frontend system. This is in particular useful when used in combination with the new `convertLegacyPlugin` utility from `@backstage/core-compat-api`. +- d001a42: Fix label related accessibility issues with `FavorityEntity` +- 012e3eb: Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + ## 1.12.3-next.3 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index f7b43a29b4..7cf7464a88 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.12.3-next.3", + "version": "1.12.3", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index b299631a21..6e254a9591 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + ## 0.0.4-next.1 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index e5f88ebba4..75a76166bd 100644 --- a/plugins/catalog-unprocessed-entities-common/package.json +++ b/plugins/catalog-unprocessed-entities-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities-common", - "version": "0.0.4-next.1", + "version": "0.0.4", "description": "Common functionalities for the catalog-unprocessed-entities plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index effa10a3ab..5b43a660d5 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + ## 0.2.7-next.1 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 1e4409c2c8..1c028e05a6 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.7-next.1", + "version": "0.2.7", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index a302ea72be..2185791a40 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog +## 1.22.0 + +### Minor Changes + +- 6925dcb: Introduces the HasSubdomainsCard component that displays the subdomains of a given domain + +### Patch Changes + +- 496b8a9: Export `RelatedEntitiesCard` presets to be reused. +- 604a504: The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 1.22.0-next.3 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index b8f8864f3c..a592ae3fd5 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.22.0-next.3", + "version": "1.22.0", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index c11499f589..28d63f63d7 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-config-schema +## 0.1.58 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.58-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index ad98b78b98..7fd576a85f 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.58-next.0", + "version": "0.1.58", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index aa952b8e0b..ae25e3b15c 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-devtools-backend +## 0.3.9 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 32a38e1: Removed unused code for lockfile analysis. +- 2886ef7: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + ## 0.3.9-next.3 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index b4aff3b838..03b3df0c00 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.9-next.3", + "version": "0.3.9", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index 80d0584c77..5cc72c62ae 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-common +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + ## 0.1.12-next.1 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index d8a87753ca..5a14202649 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-common", - "version": "0.1.12-next.1", + "version": "0.1.12", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index a19edbc26a..e1e3b11cf6 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-devtools +## 0.1.17 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-react@0.4.25 + ## 0.1.17-next.3 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 4c72b3a0cf..db6236f327 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.17-next.3", + "version": "0.1.17", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index f50553f114..64db006eea 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + ## 0.3.9-next.3 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index e2dd0ea557..89d8c17c85 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.3.9-next.3", + "version": "0.4.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index 174d3c5635..efe13729b2 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.9-next.3 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index ecbf8a018c..7a49aff69c 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.2.9-next.3", + "version": "0.2.9", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index c1789f2d62..f84238c5b9 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.9-next.3 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 0ca12ed4f6..959639fdd9 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.2.9-next.3", + "version": "0.2.9", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index cbf3353c9f..88c2ac91d8 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.9-next.3 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index b2737b01c1..10abf94085 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.2.9-next.3", + "version": "0.2.9", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 4cce2cf72c..f08743d3b3 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-github +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.9-next.3 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index b84096de5e..16238b7b93 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.2.9-next.3", + "version": "0.2.9", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index ec17ec1157..85b62d9adf 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.2.9-next.3 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index e851cdfe9a..f0183783ec 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.2.9-next.3", + "version": "0.2.9", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 936dc8b1ad..3047079949 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9 + ## 0.1.33-next.3 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index b289b3597b..0fb0092bf0 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.33-next.3", + "version": "0.1.33", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 02a6fcdba2..763699805b 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + ## 0.3.10-next.3 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 3922e370f9..a0b2b17ba1 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.10-next.3", + "version": "0.3.10", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 46908304fc..5762c94e9e 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-node +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + ## 0.3.9-next.3 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 43f42fefbf..2b51efd6af 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.9-next.3", + "version": "0.3.9", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 5cb7d4bb00..7c555c4754 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @internal/plugin-todo-list-backend +## 1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + ## 1.0.30-next.3 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 4d6a68a378..8c378a814e 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.30-next.3", + "version": "1.0.30", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index b2f2175573..4b1ba3f9ce 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + ## 1.0.21-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index cca7f5aa4b..9bb25b7054 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.21-next.1", + "version": "1.0.21", "backstage": { "role": "common-library", "pluginId": "todo-list", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 51e2b93ef9..c5ccfbcd54 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-todo-list +## 1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + ## 1.0.30-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index d950d8c406..eb13f52c6b 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.30-next.0", + "version": "1.0.30", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index d810195e7f..87ff61daf0 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-home-react +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + ## 0.1.16-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 00b0f29fca..e720402766 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.16-next.0", + "version": "0.1.16", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 9c6b1a95f6..b2435eb5db 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-home +## 0.7.9 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- fdcc059: Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16 + ## 0.7.9-next.3 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 38f0357eb8..97d7597b99 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.9-next.3", + "version": "0.7.9", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 529e0416cd..e944ce6255 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-kubernetes-backend +## 0.18.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- 8c1aa06: Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. + This was already documented and supported by the plugin. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-kubernetes-node@0.1.17 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.18.4-next.3 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 0b1271636b..978171e2ea 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.18.4-next.3", + "version": "0.18.4", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 11ab293a29..a35b71719a 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.0.14-next.3 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index db419b2da3..c7eb6658b0 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.14-next.3", + "version": "0.0.14", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 391834a694..6b3606335c 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-common +## 0.8.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + ## 0.8.2-next.2 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index c53f9af5d9..e902879f9f 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.8.2-next.2", + "version": "0.8.2", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index e24db5bb5d..6d81f07df2 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-node +## 0.1.17 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.1.17-next.3 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 4873e277c1..5f9c9efa55 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.17-next.3", + "version": "0.1.17", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 59789ef20e..d76b454a4b 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes-react +## 0.4.2 + +### Patch Changes + +- 954a593: `Liveness Probe` added in ContainerCard Component of PodDrawer +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.4.2-next.3 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 904ecc81b2..936231da7c 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.4.2-next.3", + "version": "0.4.2", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 2fd3c5ab4a..a2e47a6b1b 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-kubernetes +## 0.11.13 + +### Patch Changes + +- e6c15cc: Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.11.13-next.3 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index de70299816..c982e67d04 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.11.13-next.3", + "version": "0.11.13", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 99dcdf4f13..b040e3fe5a 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-notifications-backend-module-email +## 0.2.0 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

    ${notification.content}

    `; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- cdb630d: Add support for stream transport for debugging purposes +- 83faf24: Notification email processor supports allowing or denying specific email addresses from receiving notifications +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + ## 0.2.0-next.3 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index bfe3467dd1..9c799503e8 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.2.0-next.3", + "version": "0.2.0", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 65496509f3..bd0323d5c2 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-notifications-backend +## 0.3.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- 8013044: fix: consider broadcast union with user +- 7a05f50: Allow using notifications without users in the catalog +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + ## 0.3.4-next.3 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 501cd50beb..faf3118ef8 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.3.4-next.3", + "version": "0.3.4", "backstage": { "role": "backend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index c9b17e9fc3..1c98305038 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-notifications-node +## 0.2.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + ## 0.2.4-next.3 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 4eed1f410c..37d2663a3a 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.4-next.3", + "version": "0.2.4", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index a9c6c00929..0f257047ba 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-notifications +## 0.3.0 + +### Minor Changes + +- 0410fc9: By default, set notification as read when opening snackbar or web notification link + +### Patch Changes + +- 80b84f7: Fixed issue with notification reloading on page change +- b58e452: Broadcast notifications are now decorated with an icon. +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-react@0.0.4 + ## 0.3.0-next.1 ### Minor Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 01d49fb8e5..c847fc605d 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.3.0-next.1", + "version": "0.3.0", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index dc0d303344..4570bcf112 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org-react +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + ## 0.1.27-next.3 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index d3986a698b..607600fbaa 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.27-next.3", + "version": "0.1.27", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index bc512b3177..55b9b3431a 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-org +## 0.6.28 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.6.28-next.3 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 3190177622..3940932cbe 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.28-next.3", + "version": "0.6.28", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index e41e5ece35..6d2a77fb3c 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + ## 0.1.20-next.3 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 8ea05a53dc..637843cdeb 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.1.20-next.3", + "version": "0.1.20", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 7c39002f3d..2f43217d5d 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-permission-backend +## 0.5.47 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.5.47-next.3 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 1a80b25571..4b6287d7d2 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.47-next.3", + "version": "0.5.47", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index 8ef1eb314f..b485e9454f 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-permission-common +## 0.8.1 + +### Patch Changes + +- df784fe: Add the MetadataResponse type from @backstage/plugin-permission-node, since this + type might be used in frontend code. +- 137fa34: Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.8.1-next.1 ### Patch Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index faeaf4a972..4ea5f56814 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-common", - "version": "0.8.1-next.1", + "version": "0.8.1", "description": "Isomorphic types and client for Backstage permissions and authorization", "backstage": { "role": "common-library", diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 8fe3011f5c..79b6af785b 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-permission-node +## 0.8.1 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.8.1-next.3 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index a8d49bea69..1d3a9089aa 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.8.1-next.3", + "version": "0.8.1", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 83f4dea7ed..b92bf66ce8 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-react +## 0.4.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + ## 0.4.25-next.1 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 394f56ff02..64f9082af9 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.25-next.1", + "version": "0.4.25", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 9bb3d05258..3db9b519fa 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-proxy-backend +## 0.5.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + ## 0.5.4-next.3 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 383a8012ca..fdddaa6283 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.5.4-next.3", + "version": "0.5.4", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 2360347e41..b6610a9638 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.15 + +### Patch Changes + +- 187f583: Added examples for publish:azure action and updated its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.15-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index afdb3bdd37..43cdd10a96 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.15-next.3", + "version": "0.1.15", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 34aa1dee75..dfec3e29aa 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3fca643: Added autocompletion support for resource `branches` +- d57967c: Add ability to set the initial commit message when initializing a repository using the scaffolder action. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.13-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index aab9ffc772..cfedc6048a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.13-next.3", + "version": "0.1.13", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 53cd5df1e5..8c12baad68 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- e3b64be: Added examples for publish:bitbucketServer action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.13-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index ea7f9b6e44..76d00d0ea3 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.13-next.3", + "version": "0.1.13", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index dbc4f8ef02..5f36050fff 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.2.13-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index df1bd06c4f..d222e24fc3 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.13-next.3", + "version": "0.2.13", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index dd42aa963e..3fbba8f6da 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.24 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.2.24-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 3910f5cd5d..a85e032cea 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.24-next.3", + "version": "0.2.24", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index f8e3b566e3..38b4185f16 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.47 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.47-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index f412ff11b1..4e30c0cf98 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.47-next.3", + "version": "0.2.47", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index dac03aa7f5..46c3fe7957 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.1-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index a8035eb2ff..71f16f4b96 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.1.1-next.3", + "version": "0.1.1", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index e4c05d440f..7db14044e9 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.15 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.15-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 9221c6893f..3557ac6719 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.15-next.3", + "version": "0.1.15", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 45a93e76a9..dd0f55ce6c 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 24de005: Added test cases for publish:gitea examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.13-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 2e9f648336..51055aae54 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.13-next.3", + "version": "0.1.13", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 0d5924a780..3c96c7b73d 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.4.1 + +### Patch Changes + +- d21d307: Added examples for github:environment:create action and improve its test cases +- 6d4cb97: Added examples for github:repo:create action and improved test cases +- cd203f1: Added examples for action github:pages and improved its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.4.1-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index de6fb2d2a0..e8dd77bd5f 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.4.1-next.3", + "version": "0.4.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 417f6ffca7..4c9aa96b19 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.5 + +### Patch Changes + +- da97131: Added test cases for gitlab:issues:create examples +- fad1b90: Allow the `createGitlabProjectVariableAction` to use oauth tokens +- aab708e: Added test cases for gitlab:issue:edit examples +- ef742dc: Added test cases for gitlab:projectAccessToken:create example +- 1ba4c2f: Added test cases for gitlab:pipeline:trigger examples +- a6603e4: Add custom action for merge request: **auto** + + The **Auto** action selects the committed action between _create_ and _update_. + + The **Auto** action fetches files using the **/projects/repository/tree endpoint**. + After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.4.5-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 37eaf5b7ac..8db4f1ade5 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.4.5-next.3", + "version": "0.4.5", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 00d3cfa3eb..c6feaf8741 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.6 + +### Patch Changes + +- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-notifications-common@0.0.5 + ## 0.0.6-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index dd35dc9864..58c70478f2 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.6-next.3", + "version": "0.0.6", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index d01f0ff03e..1cb678999e 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.40 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 449def7: Add examples for fetch:rails scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.4.40-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 7f7d570962..f92cdb2c4f 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.40-next.3", + "version": "0.4.40", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index fe19c66f69..3a1c970c87 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.31 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 382e868: Added test cases for sentry:project:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.31-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index f919dd5fcb..9c2f40628e 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.31-next.3", + "version": "0.1.31", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index d760efd3ef..97fdd27e4a 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10 + ## 0.3.7-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index b766612f0d..2599de5921 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.3.7-next.3", + "version": "0.3.7", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 5b4fdade86..afa0291723 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-scaffolder-backend +## 1.24.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- dcd6a79: Added OpenTelemetry support to Scaffolder metrics + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- b63d378: Update internal imports +- ef87e06: Fix scaffolder action `catalog:write` to write to directories that don't already exist +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 1.23.1-next.3 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 61862ce636..25b717b6ca 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.23.1-next.3", + "version": "1.24.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 15a85c8aae..7e121a4e93 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-common +## 1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + ## 1.5.5-next.2 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 5f54e29d8e..2dced65144 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.5-next.2", + "version": "1.5.5", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 305f6073c5..dd4ed1e2a1 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.24.0 + - @backstage/backend-test-utils@0.5.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + ## 0.1.10-next.3 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index b5cef2fe7d..9675a689ab 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.10-next.3", + "version": "0.1.10", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 5dd3145be7..d04d7f16e0 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-scaffolder-node +## 0.4.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 0.4.9-next.3 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 375612b05c..52964f814a 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.9-next.3", + "version": "0.4.9", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 93706dbd45..57799ab836 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-scaffolder-react +## 1.11.0 + +### Minor Changes + +- 8839381: Add scaffolder option to display object items in separate rows on review page + +### Patch Changes + +- 072c00c: Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- d0e95a7: Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps` +- 4670f06: support `ajv-errors` for scaffolder validation to allow for customizing the error messages +- 04759f2: Fix null check in `isJsonObject` utility function for scaffolder review state component +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 1.11.0-next.3 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 634d85eccf..97e04c8d42 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.11.0-next.3", + "version": "1.11.0", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 8fb26190c4..409ae4734f 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-scaffolder +## 1.24.0 + +### Minor Changes + +- 1552c33: Changed the way to display entities in `MyGroupsPicker` to use `entityPresentationApi` and make it consistent across scaffolder pickers +- 3fca643: Added field extension `RepoBranchPicker` that supports autocompletion for Bitbucket + +### Patch Changes + +- 47ed51b: Add an extra bit of height to the EntityPicker dropdown to make it clear there are more options to select from, and to remove the scroll bar when there is less than 10 options +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 2ae63cd: add i18n for scaffolder +- d18f4eb: Fix undefined in the title of Scaffolder Runs on the page load +- 896a22d: Fix helper text margin for scaffolder EntityNamePicker and EntityTagsPicker when using outlined text field +- bbd9f56: Cleaned up codebase of RepoUrlPicker +- b8600fe: Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 1.24.0-next.3 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 76b6aa2d93..48fe404d31 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.24.0-next.3", + "version": "1.24.0", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 8f65a551e5..3c737cb65e 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-search-backend-module-catalog +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.1.29-next.3 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 2b17facb38..fadfc99535 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.29-next.3", + "version": "0.2.0", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index a2c01fd933..70cdbc0e93 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + ## 1.5.4-next.3 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index f0905099e0..e166cacb83 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.5.4-next.3", + "version": "1.5.4", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index f2f7930a39..41062f760d 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-search-backend-module-explore +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 93fc1a0: Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.4`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + ## 0.1.29-next.3 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index f7adba41a8..1f7611c46d 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.29-next.3", + "version": "0.2.0", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index c57129d4e0..6c96b4e32c 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.33 + +### Patch Changes + +- 7251567: Removing `@backstage/backend-app-api` dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + ## 0.5.33-next.3 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 1595100567..6005cf3409 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.33-next.3", + "version": "0.5.33", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index a480f173af..35ff0312ab 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + ## 0.1.16-next.3 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 3c104a4c96..36d948c34e 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.16-next.3", + "version": "0.2.0", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 644e080510..de280174fa 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.1.28-next.3 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 540c64dcc2..cb95652355 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.28-next.3", + "version": "0.2.0", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index a87c09cc8e..3c566e48d3 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-search-backend-node +## 1.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 3123c16: Fix package metadata +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 1.2.28-next.3 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 29b3707515..e951243136 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.28-next.3", + "version": "1.3.0", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index c31002bede..679c2777ef 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-search-backend +## 1.5.15 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.5.15-next.3 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 4390dd658c..60545dbf95 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.15-next.3", + "version": "1.5.15", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 9b6f2ed89c..3bdf90fe85 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-common +## 1.2.14 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + ## 1.2.14-next.1 ### Patch Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 073f5ead39..10524c1515 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-common", - "version": "1.2.14-next.1", + "version": "1.2.14", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", "backstage": { "role": "common-library", diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index 0815bf5b37..fa409b56b0 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-search-react +## 1.7.14 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 3123c16: Fix package metadata +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-search-common@1.2.14 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 1.7.14-next.3 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 887b41cb83..5a77cd3840 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.7.14-next.3", + "version": "1.7.14", "backstage": { "role": "web-library", "pluginId": "search", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 739cf34487..526354244a 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-search +## 1.4.15 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 3123c16: Fix package metadata +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 1.4.15-next.3 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 27f0592de7..3dbe5931e9 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.15-next.3", + "version": "1.4.15", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 455d1ee0cb..e6d8d44aa3 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-signals-backend +## 0.1.9 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-signals-node@0.1.9 + ## 0.1.9-next.3 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index bba7a6a902..2bac39bb30 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.9-next.3", + "version": "0.1.9", "backstage": { "role": "backend-plugin", "pluginId": "signals", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 606114c858..d0e1a31ae6 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-signals-node +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + ## 0.1.9-next.3 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index c4a6205944..98bce96f68 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-node", - "version": "0.1.9-next.3", + "version": "0.1.9", "description": "Node.js library for the signals plugin", "backstage": { "role": "node-library", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index 1f6c9fccf7..1ccf349862 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-signals +## 0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + ## 0.0.9-next.0 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 54af5cb9f6..ffd72fd2fd 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.9-next.0", + "version": "0.0.9", "backstage": { "role": "frontend-plugin", "pluginId": "signals", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 67e36f9d7b..dff41d1b88 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.5.10 + - @backstage/plugin-techdocs-react@1.2.7 + ## 1.0.37-next.3 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 949ba5e644..b41d9652ca 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.37-next.3", + "version": "1.0.37", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 355ae0703f..745071c589 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-techdocs-backend +## 1.10.10 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b77fbf4: Added back `type: 'local'` to TechDocs config schema for `publisher` +- a16632c: Update configuration schema to match actual behavior +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + ## 1.10.10-next.3 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 074e0be91d..f1312f1e05 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.10-next.3", + "version": "1.10.10", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", diff --git a/plugins/techdocs-common/CHANGELOG.md b/plugins/techdocs-common/CHANGELOG.md index e6d454e557..619884e4ec 100644 --- a/plugins/techdocs-common/CHANGELOG.md +++ b/plugins/techdocs-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-techdocs-common +## 0.1.0 + +### Minor Changes + +- 4698e1f: Initial release of the techdocs-common package. + ## 0.1.0-next.0 ### Minor Changes diff --git a/plugins/techdocs-common/package.json b/plugins/techdocs-common/package.json index e8d2692c0f..f0e3219d6b 100644 --- a/plugins/techdocs-common/package.json +++ b/plugins/techdocs-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-common", - "version": "0.1.0-next.0", + "version": "0.1.0", "description": "Common functionality for TechDocs", "backstage": { "role": "common-library", diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 86010fb23a..1d73053f4e 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/integration@1.14.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-techdocs-react@1.2.7 + ## 1.1.13-next.1 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 86f60bbf3a..a682740923 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.13-next.1", + "version": "1.1.13", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module", diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index a17ba53a5f..8a12d40ffb 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-techdocs-node +## 1.12.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 69bd940: Use annotation constants from new techdocs-common package. +- 949083d: Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently. +- 5cedd9f: Fix TechDocs Edit URL for nested docs +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + ## 1.12.9-next.3 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 709803af84..edef992e6b 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.9-next.3", + "version": "1.12.9", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library", diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index eadb950e84..0163217d65 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-react +## 1.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + ## 1.2.7-next.1 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index dc21b720f4..4c5a013fcd 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.7-next.1", + "version": "1.2.7", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 3f9471f12c..d1b1a559b5 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-techdocs +## 1.10.8 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 27794d1: Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. +- 4490d73: Refactor TechDocs' mkdocs-redirects support. +- 8543e72: TechDocs redirect feature now includes a notification to the user before they are redirected. +- 67e76f2: TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. +- bdc5471: Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + ## 1.10.8-next.3 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 9a9586008d..525ba58caa 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.10.8-next.3", + "version": "1.10.8", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin", diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index af615afdfb..0a6359e1c8 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-user-settings-backend +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-node@0.1.9 + - @backstage/plugin-user-settings-common@0.0.1 + ## 0.2.22-next.3 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index f93bb24a33..ab91735143 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.22-next.3", + "version": "0.2.22", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 4f007e3da0..71182f1289 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-user-settings +## 0.8.11 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + ## 0.8.11-next.3 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 756555f8ef..1e2972674d 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.11-next.3", + "version": "0.8.11", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin", diff --git a/yarn.lock b/yarn.lock index 02e8341b26..6c6d884fec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3843,18 +3843,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-client@npm:^1.6.5": - version: 1.6.5 - resolution: "@backstage/catalog-client@npm:1.6.5" - dependencies: - "@backstage/catalog-model": ^1.5.0 - "@backstage/errors": ^1.2.4 - cross-fetch: ^4.0.0 - uri-template: ^2.0.0 - checksum: afb84382c7a8e9124090d56ec4a3a1e8ab7dfda33d337851412b9ee4fca0e85fb7263729d6eb4efa8c3198343ed03843ef468492f74401951542908534febfad - languageName: node - linkType: hard - "@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" @@ -3868,19 +3856,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.5.0": - version: 1.5.0 - resolution: "@backstage/catalog-model@npm:1.5.0" - dependencies: - "@backstage/errors": ^1.2.4 - "@backstage/types": ^1.1.1 - ajv: ^8.10.0 - lodash: ^4.17.21 - checksum: 545873625afbb25a2142af9f8c701547b448fe8b822c9ed699c86a9c385571014115a2c3105a3dca2bc2ac63b837b093dba39a973c2f9e23521d427a0328ba12 - languageName: node - linkType: hard - -"@backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@^1.4.3, @backstage/catalog-model@^1.5.0, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -4133,7 +4109,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.1.1, @backstage/config@^1.2.0, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@^1.1.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -4207,107 +4183,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.13.10": - version: 0.13.10 - resolution: "@backstage/core-components@npm:0.13.10" - dependencies: - "@backstage/config": ^1.1.1 - "@backstage/core-plugin-api": ^1.8.2 - "@backstage/errors": ^1.2.3 - "@backstage/theme": ^0.5.0 - "@backstage/version-bridge": ^1.0.7 - "@date-io/core": ^1.3.13 - "@material-table/core": ^3.1.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^23.0.0 - "@types/react": ^16.13.1 || ^17.0.0 - "@types/react-sparklines": ^1.7.0 - "@types/react-text-truncate": ^0.14.0 - ansi-regex: ^6.0.1 - classnames: ^2.2.6 - d3-selection: ^3.0.0 - d3-shape: ^3.0.0 - d3-zoom: ^3.0.0 - dagre: ^0.8.5 - linkify-react: 4.1.3 - linkifyjs: 4.1.3 - lodash: ^4.17.21 - pluralize: ^8.0.0 - qs: ^6.9.4 - rc-progress: 3.5.1 - react-helmet: 6.1.0 - react-hook-form: ^7.12.2 - react-idle-timer: 5.6.2 - react-markdown: ^8.0.0 - react-sparklines: ^1.7.0 - react-syntax-highlighter: ^15.4.5 - react-text-truncate: ^0.19.0 - react-use: ^17.3.2 - react-virtualized-auto-sizer: ^1.0.11 - react-window: ^1.8.6 - remark-gfm: ^3.0.1 - zen-observable: ^0.10.0 - zod: ^3.22.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 - languageName: node - linkType: hard - -"@backstage/core-components@npm:^0.14.9": - version: 0.14.9 - resolution: "@backstage/core-components@npm:0.14.9" - dependencies: - "@backstage/config": ^1.2.0 - "@backstage/core-plugin-api": ^1.9.3 - "@backstage/errors": ^1.2.4 - "@backstage/theme": ^0.5.6 - "@backstage/version-bridge": ^1.0.8 - "@date-io/core": ^1.3.13 - "@material-table/core": ^3.1.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^24.0.0 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - "@types/react-sparklines": ^1.7.0 - ansi-regex: ^6.0.1 - classnames: ^2.2.6 - d3-selection: ^3.0.0 - d3-shape: ^3.0.0 - d3-zoom: ^3.0.0 - dagre: ^0.8.5 - linkify-react: 4.1.3 - linkifyjs: 4.1.3 - lodash: ^4.17.21 - pluralize: ^8.0.0 - qs: ^6.9.4 - rc-progress: 3.5.1 - react-helmet: 6.1.0 - react-hook-form: ^7.12.2 - react-idle-timer: 5.7.2 - react-markdown: ^8.0.0 - react-sparklines: ^1.7.0 - react-syntax-highlighter: ^15.4.5 - react-use: ^17.3.2 - react-virtualized-auto-sizer: ^1.0.11 - react-window: ^1.8.6 - remark-gfm: ^3.0.1 - zen-observable: ^0.10.0 - zod: ^3.22.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: cf89c9e19dd33270bdc2bb9e7d89487c71b78e6645dcef9a6ac0d1df67b52e4eee61f2873abe093ed921d756a75f97e870c294aa5c2a730598e520b06e38b41f - languageName: node - linkType: hard - -"@backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@^0.14.9, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -4378,6 +4254,57 @@ __metadata: languageName: unknown linkType: soft +"@backstage/core-components@npm:^0.13.10": + version: 0.13.10 + resolution: "@backstage/core-components@npm:0.13.10" + dependencies: + "@backstage/config": ^1.1.1 + "@backstage/core-plugin-api": ^1.8.2 + "@backstage/errors": ^1.2.3 + "@backstage/theme": ^0.5.0 + "@backstage/version-bridge": ^1.0.7 + "@date-io/core": ^1.3.13 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^23.0.0 + "@types/react": ^16.13.1 || ^17.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + linkify-react: 4.1.3 + linkifyjs: 4.1.3 + lodash: ^4.17.21 + pluralize: ^8.0.0 + qs: ^6.9.4 + rc-progress: 3.5.1 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-idle-timer: 5.6.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.11 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.10.0 + zod: ^3.22.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 + languageName: node + linkType: hard + "@backstage/core-plugin-api@^1.8.2, @backstage/core-plugin-api@^1.9.3, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" @@ -4525,26 +4452,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-plugin-api@npm:^0.6.7": - version: 0.6.7 - resolution: "@backstage/frontend-plugin-api@npm:0.6.7" - dependencies: - "@backstage/core-components": ^0.14.9 - "@backstage/core-plugin-api": ^1.9.3 - "@backstage/types": ^1.1.1 - "@backstage/version-bridge": ^1.0.8 - "@material-ui/core": ^4.12.4 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - lodash: ^4.17.21 - zod: ^3.22.4 - zod-to-json-schema: ^3.21.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: cf7485aedcae02c5855a62adea6fffcfad256d614731ade38d2bebd5f98e91aa5d1020cd9ac25b74d67d2681c010addff4a89d5be1e2deb2e688bf1915134c32 - languageName: node - linkType: hard - "@backstage/frontend-plugin-api@workspace:^, @backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" @@ -4609,24 +4516,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.29": - version: 1.1.29 - resolution: "@backstage/integration-react@npm:1.1.29" - dependencies: - "@backstage/config": ^1.2.0 - "@backstage/core-plugin-api": ^1.9.3 - "@backstage/integration": ^1.13.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@types/react": ^16.13.1 || ^17.0.0 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: add10ad8c108933354037f9d74ec554174d2af53d74a031e12ed01e98bd51481413b93c694460cf045b3bae818dd18b5efa061fc60c210e145e4629e0cb227b7 - languageName: node - linkType: hard - "@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" @@ -4651,23 +4540,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration@npm:^1.13.0": - version: 1.13.0 - resolution: "@backstage/integration@npm:1.13.0" - dependencies: - "@azure/identity": ^4.0.0 - "@backstage/config": ^1.2.0 - "@backstage/errors": ^1.2.4 - "@octokit/auth-app": ^4.0.0 - "@octokit/rest": ^19.0.3 - cross-fetch: ^4.0.0 - git-url-parse: ^14.0.0 - lodash: ^4.17.21 - luxon: ^3.0.0 - checksum: 2b824261b0d30771e54515f8571c2af0b4c64f5cd3d1f990c234152b19e81d1e4ddbbbfae2a0b74f44abbfb67f52609c7eee9dc9fbb61d5f0076acaa80b3e670 - languageName: node - linkType: hard - "@backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" @@ -5728,18 +5600,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@npm:^1.0.25": - version: 1.0.25 - resolution: "@backstage/plugin-catalog-common@npm:1.0.25" - dependencies: - "@backstage/catalog-model": ^1.5.0 - "@backstage/plugin-permission-common": ^0.8.0 - "@backstage/plugin-search-common": ^1.2.13 - checksum: 37078811a78423accc078ae43d1572de6e74e0daff6b03a3d6f30d3ce37e71d30f60ac90aea5cf442ca0d2123259b267b37b966e29aa4f2dba08845d562d0e68 - languageName: node - linkType: hard - -"@backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@^1.0.20, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -5848,43 +5709,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.12.2, @backstage/plugin-catalog-react@npm:^1.9.3": - version: 1.12.2 - resolution: "@backstage/plugin-catalog-react@npm:1.12.2" - dependencies: - "@backstage/catalog-client": ^1.6.5 - "@backstage/catalog-model": ^1.5.0 - "@backstage/core-components": ^0.14.9 - "@backstage/core-plugin-api": ^1.9.3 - "@backstage/errors": ^1.2.4 - "@backstage/frontend-plugin-api": ^0.6.7 - "@backstage/integration-react": ^1.1.29 - "@backstage/plugin-catalog-common": ^1.0.25 - "@backstage/plugin-permission-common": ^0.8.0 - "@backstage/plugin-permission-react": ^0.4.24 - "@backstage/types": ^1.1.1 - "@backstage/version-bridge": ^1.0.8 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^24.0.0 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - classnames: ^2.2.6 - lodash: ^4.17.21 - material-ui-popup-state: ^1.9.3 - qs: ^6.9.4 - react-use: ^17.2.4 - yaml: ^2.0.0 - zen-observable: ^0.10.0 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 8570e71e56e89cc8ba890ad9cd1f2ce22147635e9a326703291ee8a75d704086c4a31cd11b2b2c3eb54f3e1dc82822b1bc7b9317e8c6d81f6ab9758c2c01ec5a - languageName: node - linkType: hard - -"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@^1.12.2, @backstage/plugin-catalog-react@^1.9.3, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -6734,20 +6559,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@npm:^0.8.0": - version: 0.8.0 - resolution: "@backstage/plugin-permission-common@npm:0.8.0" - dependencies: - "@backstage/config": ^1.2.0 - "@backstage/errors": ^1.2.4 - "@backstage/types": ^1.1.1 - cross-fetch: ^4.0.0 - uuid: ^9.0.0 - zod: ^3.22.4 - checksum: 03d706cf764d65319fd32156438b6f826568c7d51e79fc6e7cd951b023017a1e07c7380f2babec7efd2ed96f441485ee68072d06fb5e42a1bf7aba7cd1f6ce6f - languageName: node - linkType: hard - "@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" @@ -6787,23 +6598,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-react@npm:^0.4.24": - version: 0.4.24 - resolution: "@backstage/plugin-permission-react@npm:0.4.24" - dependencies: - "@backstage/config": ^1.2.0 - "@backstage/core-plugin-api": ^1.9.3 - "@backstage/plugin-permission-common": ^0.8.0 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - swr: ^2.0.0 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 6fa895f4a25f7f08642f64a9b4b41e5f3adfbf3c3f1ede9e1abc7bb08f532d9d0c9baf9f08b472fff54803a2c2aaeff2306b5e6d1514193a2407af4b12433d3a - languageName: node - linkType: hard - "@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" @@ -7578,16 +7372,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-search-common@npm:^1.2.13": - version: 1.2.13 - resolution: "@backstage/plugin-search-common@npm:1.2.13" - dependencies: - "@backstage/plugin-permission-common": ^0.8.0 - "@backstage/types": ^1.1.1 - checksum: 0c7b7b791b648e874081b845488f636791754bc4da1a4780b49e7281cc6294bcad0a9feab9fd7dd5f6a583b5fce372e704f440ad7bc33ab828ebd2afc5344504 - languageName: node - linkType: hard - "@backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": version: 0.0.0-use.local resolution: "@backstage/plugin-search-common@workspace:plugins/search-common" @@ -8149,7 +7933,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.5.0, @backstage/theme@^0.5.6, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@^0.5.0, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -8180,7 +7964,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@^1.0.8, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": +"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": version: 0.0.0-use.local resolution: "@backstage/version-bridge@workspace:packages/version-bridge" dependencies: From fd31faa6837096fa81c4e4e35ec7249ffbeeeeba Mon Sep 17 00:00:00 2001 From: Simone Fumagalli Date: Tue, 20 Aug 2024 10:59:49 +0200 Subject: [PATCH 373/393] Fixed markdown issue Fixed markdown issue that was breaking the page Signed-off-by: Simone Fumagalli --- ADOPTERS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index abab08871d..76114b6965 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -134,7 +134,6 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [SafetyCulture](https://safetyculture.com/) | [@R-cen](https://github.com/R-cen), [@lachlancooper](https://github.com/lachlancooper), [@hkf57](https://github.com/hkf57) | Internal developer portal to provide a centralized place for engineers to see an overview of their team's services and information related to the service from other systems. Initially focused on the software catalog, techdocs and search. | | [Sana Life Science](https://sanalifescience.com) | [Joe Hillyard](mailto:joe@sanalifescience.com) | API Catalog, Tools Management & Control Hub | | [Ndustrial](https://ndustrial.io) | [Jonathan Skubic](mailto:jonathan@ndustrial.io) | Software Project Catalog | - | [Kambi AB](https://www.kambi.com) | [Martin Norum](mailto:martin.norum@kambi.com) | We want to kick ass at speed, so we're currently building up a catalog of our existing software, and looking into how Backstage can support us in our journey towards autonomous product teams. Both to improve speed to market and operational awareness. | | [ANZ](https://www.anz.com.au/personal/) | [Elliot Jackson](mailto:elliot.jackson@anz.com) | Catalog, tech docs and automation | | [Genie Solutions](https://www.geniesolutionssoftware.com.au) | [Zainab Bagasrawala](mailto:zainabbagasrawala@geniesolutions.com.au) | Developer Portal to track our projects, documentation, observability tools and more | From 5b9786d9d63ba5685a728f732c84a70fafeda110 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Aug 2024 12:07:50 +0200 Subject: [PATCH 374/393] docs: release notes for 1.30 Signed-off-by: Patrik Oldsberg --- docs/releases/v1.30.0.md | 117 +++++++++++++++++++++++++++++++++ microsite/docusaurus.config.ts | 2 +- microsite/sidebars.json | 1 + 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 docs/releases/v1.30.0.md diff --git a/docs/releases/v1.30.0.md b/docs/releases/v1.30.0.md new file mode 100644 index 0000000000..e441ce9687 --- /dev/null +++ b/docs/releases/v1.30.0.md @@ -0,0 +1,117 @@ +--- +id: v1.30.0 +title: v1.30.0 +description: Backstage Release v1.30.0 +--- + +These are the release notes for the v1.30.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### New Frontend System - Plugin Adoption + +This release marks another big milestone for the New Frontend System. **We encourage all plugin owners to [add support for the new frontend system](https://backstage.io/docs/frontend-system/building-plugins/migrating) to their plugins.** + +At the end of last year in the [1.21 release](https://backstage.io/docs/releases/v1.21.0), we shipped the New Frontend System Alpha. It marked a more stable release of the new system, but we knew there was still much more work left to be done. Since then we have received valuable feedback and identified key areas of improvement. In particular around the creation of new extension kinds as well as overriding and testing of extensions. + +Over the summer months we’ve been working hard towards addressing this feedback and getting the New Frontend System in shape for us to be confident in encouraging broader adoption by plugins. For a summary of the changes you can check out the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130), or can see the ongoing progress in the [meta issue](https://github.com/backstage/backstage/issues/19545). With this release comes some new features, deprecations and breaking changes. + +**Breaking**: + +- All types of route references in the New Frontend System are now optional. This means that all usages of `useRouteRef` in the new system might return `undefined`, and your code must be able to act accordingly. Code that uses the old system (which will be the vast majority of code at this point) is **not** affected by this. + +**New**: + +- Blueprints and `createExtensionBlueprint` as a replacement for extension creators. You will find that old extension creators are marked as deprecated, and point to the corresponding blueprints which have a superior developer experience! +- Ability to override individual extensions using `extension.override(...)`, as well as overriding extensions in a plugin using a combination of `plugin.withOverrides([...])` and `plugin.getExtension(id)` to replace individual extensions or add new ones. +- `createExtensionTester` supports `.get` and `.query` to directly access extension data and streamline tests decoupled from React, as well as a `.reactElement` shorthand for accessing any output React elements. +- A new set of utilities that can patch support for the new frontend system for a plugin that otherwise only supports the old one. The new `convertLegacyPlugin` is used to convert a plugin instance, while `convertLegacyPageExtension` with friends can convert extensions from the old system. + +**Deprecations**: + +- `createPlugin` has been renamed to `createFrontendPlugin` +- `createExtension` with object keys for `inputs` and `outputs` has been deprecated in favor of the array form. +- `configSchema` in `createExtension` has been replaced with `config.schema` which is a better alternative to declaring config for extensions without having to use `createSchemaFromZod`. +- Existing `dataRefs` should now embed the ID using the `.with` method. +- The `render` method on `createExtensionTester` has been deprecated in favor of composing `.reactElement` with `renderInTestApp`. + +### BREAKING: Backend System deprecations and removals + +- Almost all service factories in `@backstage/backend-app-api` were marked deprecated some time back - those are now removed. Please import them from their new homes in `@backstage/backend-defaults/` instead. +- In our effort to migrate to the new backend system some backend-plugins `createRouter` exports have been marked as deprecated. Please make sure to update your backends accordingly as `createRouter` will eventually be removed from all plugin exports. +- Several deprecated methods and types have been removed from backend related packages. Most of these are either renamed and re-exported from other packages, see the CHANGELOG for the individual package for more information. + +Most notably the long deprecated `UrlReader` exports have been renamed: + +- `ReadUrlOptions`: Use `UrlReaderServiceReadUrlOptions` instead; +- `ReadUrlResponse`: Use `UrlReaderServiceReadUrlResponse` instead; +- `ReadTreeOptions`: Use `UrlReaderServiceReadTreeOptions` instead; +- `ReadTreeResponse`: Use `UrlReaderServiceReadTreeResponse` instead; +- `ReadTreeResponseFile`: Use `UrlReaderServiceReadTreeResponseFile` instead; +- `ReadTreeResponseDirOptions`: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; +- `SearchOptions`: Use `UrlReaderServiceSearchOptions` instead; +- `SearchResponse`: Use `UrlReaderServiceSearchResponse` instead; +- `SearchResponseFile`: Use `UrlReaderServiceSearchResponseFile` instead. + +### BREAKING: Auth Sign In Resolver Priority + +Sign-in resolvers configured via `.signIn.resolvers` in your app-config now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default ones, which you can then override through configuration, simplifying deploying the same code in multiple environments. + +### BREAKING: `@backstage/cli` + +The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. This means that `versions:bump` will no longer attempt to deduplicate after bumping and modifying the lockfile. + +The `versions:check` command has also been removed as its only purpose was to verify and mutate the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic, as a replacement. This change was made in order for us to support other Package Managers in the future and remove the coupling with `yarn`. + +### BREAKING: `@backstage/backend-test-utils` + +- `setupRequestMockHandlers` is removed; use `CreateMockDirectoryOptions` instead. +- `CreateMockDirectoryOptions` is removed; use `registerMswTestHooks` instead. +- Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. +- Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Scaffolder Internationalization + +Thanks to [@mario-mui](https://github.com/mario-mui) we now have i18n support for another core feature! Contributed in [#25827](https://github.com/backstage/backstage/pull/25827) + +### Dynamic Backend Feature Loaders + +You can now use `createBackendFeatureLoader` to dynamically load features in the backend, for example based on runtime configuration, and many other exciting possibilities. Check out [the docs](https://backstage.io/docs/backend-system/architecture/feature-loaders)! + +## Security Fixes + +The AWS ALB auth provider now has a configuration option `signer`, which should be set to the ARN of your ALB instance. We strongly recommend that you set this configuration value, since it will help strengthen your installation. + +Example: + +```diff + auth: + providers: + awsalb: + issuer: … + # put your actual ARN here ++ signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + region: ... + signIn: + resolvers: + - resolver: ... +``` + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.30.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 67f6de4962..67018c6e33 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -241,7 +241,7 @@ const config: Config = { position: 'left', }, { - to: 'docs/releases/v1.29.0', + to: 'docs/releases/v1.30.0', label: 'Releases', position: 'left', }, diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 1c1bb8df81..92567c844d 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,7 @@ { "releases": { "Release Notes": [ + "releases/v1.30.0", "releases/v1.29.0", "releases/v1.28.0", "releases/v1.27.0", From af5796f5c38a6b4de7a4bffaf5ecc97f8d9a4513 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:48:25 +0200 Subject: [PATCH 375/393] frontend-plugin-api: remove support for v1 extensions Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 219 +++++++---------- .../src/wiring/createExtension.ts | 225 +++++------------- .../src/wiring/createExtensionInput.test.ts | 42 +--- .../src/wiring/createExtensionInput.ts | 78 ++---- .../wiring/createExtensionOverrides.test.ts | 16 +- .../src/wiring/createFrontendPlugin.test.ts | 77 +++--- .../frontend-plugin-api/src/wiring/index.ts | 5 - .../src/wiring/resolveExtensionDefinition.ts | 27 ++- 8 files changed, 236 insertions(+), 453 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 5902c5c3e7..5853f28004 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -21,6 +21,9 @@ import { createExtensionInput } from './createExtensionInput'; const stringDataRef = createExtensionDataRef().with({ id: 'string' }); const numberDataRef = createExtensionDataRef().with({ id: 'number' }); +const booleanDataRef = createExtensionDataRef().with({ + id: 'boolean', +}); function unused(..._any: any[]) {} @@ -29,42 +32,35 @@ describe('createExtension', () => { const baseConfig = { namespace: 'test', attachTo: { id: 'root', input: 'default' }, - output: { - foo: stringDataRef, - }, + output: [stringDataRef], }; const extension = createExtension({ ...baseConfig, factory() { - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); - // When declared as an error function without a block the TypeScript errors - // are a more specific and will often point at the property that is problematic. + // Member arrow function declaration + createExtension({ + ...baseConfig, + factory: () => [ + stringDataRef( + // @ts-expect-error + 3, + ), + ], + }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 3, - }), + factory: () => [numberDataRef(3)], }); + // @ts-expect-error createExtension({ ...baseConfig, - factory: () => - // @ts-expect-error - ({ - bar: 'bar', - }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({}), + factory: () => [], }); createExtension({ ...baseConfig, @@ -79,39 +75,37 @@ describe('createExtension', () => { 'bar', }); - // When declared as a function with a block the TypeScript error will instead - // be tied to the factory function declaration itself, but the error messages - // is still helpful and points to part of the return type that is problematic. + // Method declaration createExtension({ ...baseConfig, - // @ts-expect-error factory() { - return { - foo: 3, - }; + return [ + stringDataRef( + // @ts-expect-error + 3, + ), + ]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory() { + return [numberDataRef(3)]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory() { + return []; }, }); createExtension({ ...baseConfig, // @ts-expect-error factory() { - return { - bar: 'bar', - }; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory() { - return {}; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory() { - return {}; + return; }, }); createExtension({ @@ -122,36 +116,37 @@ describe('createExtension', () => { }, }); + // Member function declaration createExtension({ ...baseConfig, - // @ts-expect-error factory: () => { - return { - foo: 3, - }; + return [ + stringDataRef( + // @ts-expect-error + 3, + ), + ]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory: () => { + return [numberDataRef(3)]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory: () => { + return []; }, }); createExtension({ ...baseConfig, // @ts-expect-error factory: () => { - return { - bar: 'bar', - }; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory: () => { - return {}; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory: () => { - return {}; + return; }, }); createExtension({ @@ -167,52 +162,27 @@ describe('createExtension', () => { const baseConfig = { namespace: 'test', attachTo: { id: 'root', input: 'default' }, - output: { - foo: stringDataRef, - bar: stringDataRef.optional(), - }, + output: [stringDataRef, numberDataRef.optional()], }; const extension = createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - }), + factory: () => [stringDataRef('bar')], }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - bar: 'baz', - }), + factory: () => [stringDataRef('bar'), numberDataRef(3)], }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 3, - }), + factory: () => [numberDataRef(3)], }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - bar: 3, - }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({ bar: 'bar' }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({}), + factory: () => [], }); createExtension({ ...baseConfig, @@ -238,57 +208,48 @@ describe('createExtension', () => { namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { - mixed: createExtensionInput({ - required: stringDataRef, - optional: stringDataRef.optional(), - }), - onlyRequired: createExtensionInput({ - required: stringDataRef, - }), - onlyOptional: createExtensionInput({ - optional: stringDataRef.optional(), - }), - }, - output: { - foo: stringDataRef, + mixed: createExtensionInput([stringDataRef, numberDataRef.optional()]), + onlyRequired: createExtensionInput([stringDataRef]), + onlyOptional: createExtensionInput([stringDataRef.optional()]), }, + output: [stringDataRef], factory({ inputs }) { - const a1: string = inputs.mixed?.[0].output.required; + const a1: string = inputs.mixed?.[0].get(stringDataRef); // @ts-expect-error - const a2: number = inputs.mixed?.[0].output.required; + const a2: number = inputs.mixed?.[0].get(stringDataRef); // @ts-expect-error - const a3: any = inputs.mixed?.[0].output.nonExistent; + const a3: any = inputs.mixed?.[0].get(booleanDataRef); unused(a1, a2, a3); - const b1: string | undefined = inputs.mixed?.[0].output.optional; + const b1: number | undefined = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b2: string = inputs.mixed?.[0].output.optional; + const b2: string = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b3: number = inputs.mixed?.[0].output.optional; + const b3: number = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b4: number | undefined = inputs.mixed?.[0].output.optional; + const b4: string | undefined = inputs.mixed?.[0].get(numberDataRef); unused(b1, b2, b3, b4); - const c1: string = inputs.onlyRequired?.[0].output.required; + const c1: string = inputs.onlyRequired?.[0].get(stringDataRef); // @ts-expect-error - const c2: number = inputs.onlyRequired?.[0].output.required; + const c2: number = inputs.onlyRequired?.[0].get(stringDataRef); unused(c1, c2); - const d1: string | undefined = inputs.onlyOptional?.[0].output.optional; + const d1: string | undefined = + inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d2: string = inputs.onlyOptional?.[0].output.optional; + const d2: string = inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d3: number = inputs.onlyOptional?.[0].output.optional; + const d3: number = inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d4: number | undefined = inputs.onlyOptional?.[0].output.optional; + const d4: number | undefined = + inputs.onlyOptional?.[0].get(stringDataRef); unused(d1, d2, d3, d4); - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); expect(String(extension)).toBe( 'ExtensionDefinition{namespace=test,attachTo=root@default}', ); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 66b908f236..0c89ff1567 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -15,7 +15,7 @@ */ import { AppNode } from '../apis'; -import { PortableSchema, createSchemaFromZod } from '../schema'; +import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { ResolveInputValueOverrides, @@ -29,46 +29,9 @@ import { AnyExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; -import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput'; +import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; - -/** - * @public - * @deprecated Extension data maps will be removed. - */ -export type AnyExtensionDataMap = { - [name in string]: AnyExtensionDataRef; -}; - -/** - * @public - * @deprecated This type will be removed. - */ -export type AnyExtensionInputMap = { - [inputName in string]: LegacyExtensionInput< - AnyExtensionDataMap, - { optional: boolean; singleton: boolean } - >; -}; - -/** - * Converts an extension data map into the matching concrete data values type. - * @public - * @deprecated Extension data maps will be removed. - */ -export type ExtensionDataValues = { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? never - : DataName]: TExtensionData[DataName]['T']; -} & { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? DataName - : never]?: TExtensionData[DataName]['T']; -}; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; /** * Convert a single extension input into a matching resolved input. @@ -80,11 +43,6 @@ export type ResolvedExtensionInput< ? { node: AppNode; } & ExtensionDataContainer - : TExtensionInput['extensionData'] extends AnyExtensionDataMap - ? { - node: AppNode; - output: ExtensionDataValues; - } : never; /** @@ -93,7 +51,7 @@ export type ResolvedExtensionInput< */ export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput | LegacyExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] @@ -103,31 +61,6 @@ export type ResolvedExtensionInputs< : Expand | undefined>; }; -/** - * @public - * @deprecated This way of structuring the options is deprecated, this type will be removed in the future - */ -export interface LegacyCreateExtensionOptions< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, -> { - kind?: string; - namespace?: string; - name?: string; - attachTo: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - output: TOutput; - configSchema?: PortableSchema; - factory(context: { - node: AppNode; - config: TConfig; - inputs: Expand>; - }): Expand>; -} - type ToIntersection = (U extends any ? (k: U) => void : never) extends ( k: infer I, ) => void @@ -326,13 +259,27 @@ export type InternalExtensionDefinition< ( | { readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; factory(context: { node: AppNode; config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; } | { readonly version: 'v2'; @@ -419,23 +366,6 @@ export function createExtension< name: string | undefined extends TName ? undefined : TName; } >; -/** - * @public - * @deprecated - use the array format of `output` instead, see TODO-doc-link - */ -export function createExtension< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, ->( - options: LegacyCreateExtensionOptions< - TOutput, - TInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition; export function createExtension< const TKind extends string | undefined, const TNamespace extends string | undefined, @@ -447,43 +377,31 @@ export function createExtension< { optional: boolean; singleton: boolean } >; }, - TLegacyInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, >( - options: - | CreateExtensionOptions< - TKind, - TNamespace, - TName, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput - > - | LegacyCreateExtensionOptions< - AnyExtensionDataMap, - TLegacyInputs, - TConfig, - TConfigInput - >, + options: CreateExtensionOptions< + TKind, + TNamespace, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, ): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >), + string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }, + string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >, UOutput, TInputs, { @@ -492,29 +410,20 @@ export function createExtension< name: TName; } > { - if ('configSchema' in options && 'config' in options) { - throw new Error(`Cannot provide both configSchema and config.schema`); - } - let configSchema: PortableSchema | undefined; - if ('configSchema' in options) { - configSchema = options.configSchema; - } - if ('config' in options) { - const newConfigSchema = options.config?.schema; - configSchema = - newConfigSchema && - createSchemaFromZod(innerZ => - innerZ.object( - Object.fromEntries( - Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]), - ), + const schemaDeclaration = options.config?.schema; + const configSchema = + schemaDeclaration && + createSchemaFromZod(innerZ => + innerZ.object( + Object.fromEntries( + Object.entries(schemaDeclaration).map(([k, v]) => [k, v(innerZ)]), ), - ); - } + ), + ); return { $$type: '@backstage/ExtensionDefinition', - version: Symbol.iterator in options.output ? 'v2' : 'v1', + version: 'v2', kind: options.kind, namespace: options.namespace, name: options.name, @@ -687,22 +596,18 @@ export function createExtension< } as CreateExtensionOptions); }, } as InternalExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >), + string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }, + string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >, UOutput, TInputs, { diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts index e401adc675..084758b793 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts @@ -15,11 +15,7 @@ */ import { createExtensionDataRef } from './createExtensionDataRef'; -import { - ExtensionInput, - LegacyExtensionInput, - createExtensionInput, -} from './createExtensionInput'; +import { ExtensionInput, createExtensionInput } from './createExtensionInput'; const stringDataRef = createExtensionDataRef().with({ id: 'str' }); const numberDataRef = createExtensionDataRef().with({ id: 'num' }); @@ -130,40 +126,4 @@ describe('createExtensionInput', () => { createExtensionInput([stringDataRef, stringDataRef], { singleton: true }), ).toThrow("ExtensionInput may not have duplicate data refs: 'str'"); }); - - describe('old api', () => { - it('should create a regular input', () => { - const input = createExtensionInput({ - str: stringDataRef, - num: numberDataRef, - }); - expect(input).toEqual({ - $$type: '@backstage/ExtensionInput', - extensionData: { str: stringDataRef, num: numberDataRef }, - config: { singleton: false, optional: false }, - }); - - const x1: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: false; optional: false } - > = input; - // @ts-expect-error - const x2: LegacyExtensionInput< - { str: typeof numberDataRef; num: typeof stringDataRef }, // switched - { singleton: false; optional: false } - > = input; - // @ts-expect-error - const x3: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: true; optional: false } - > = input; - // @ts-expect-error - const x4: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: false; optional: true } - > = input; - - unused(x1, x2, x3, x4); - }); - }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts index b7efb13e6e..3651eb3ee5 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { AnyExtensionDataMap } from './createExtension'; import { ExtensionDataRef } from './createExtensionDataRef'; /** @public */ @@ -27,36 +26,6 @@ export interface ExtensionInput< config: TConfig; } -/** - * @public - * @deprecated This type will be removed. Use `ExtensionInput` instead. - */ -export interface LegacyExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton: boolean; optional: boolean }, -> { - $$type: '@backstage/ExtensionInput'; - extensionData: TExtensionDataMap; - config: TConfig; -} - -/** - * @public - * @deprecated Use the following form instead: `createExtensionInput([dataRef1, dataRef2])` - */ -export function createExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton?: boolean; optional?: boolean }, ->( - extensionData: TExtensionDataMap, - config?: TConfig, -): LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } ->; /** @public */ export function createExtensionInput< UExtensionData extends ExtensionDataRef, @@ -73,26 +42,17 @@ export function createExtensionInput< >; export function createExtensionInput< TExtensionData extends ExtensionDataRef, - TExtensionDataMap extends AnyExtensionDataMap, TConfig extends { singleton?: boolean; optional?: boolean }, >( - extensionData: Array | TExtensionDataMap, + extensionData: Array, config?: TConfig, -): - | LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > - | ExtensionInput< - TExtensionData, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > { +): ExtensionInput< + TExtensionData, + { + singleton: TConfig['singleton'] extends true ? true : false; + optional: TConfig['optional'] extends true ? true : false; + } +> { if (process.env.NODE_ENV !== 'production') { if (Array.isArray(extensionData)) { const seen = new Set(); @@ -124,19 +84,11 @@ export function createExtensionInput< ? true : false, }, - } as - | LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > - | ExtensionInput< - TExtensionData, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - >; + } as ExtensionInput< + TExtensionData, + { + singleton: TConfig['singleton'] extends true ? true : false; + optional: TConfig['optional'] extends true ? true : false; + } + >; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index aa00247222..ef95f82e25 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -40,22 +40,22 @@ describe('createExtensionOverrides', () => { createExtension({ name: 'a', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), createExtension({ namespace: 'b', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), createExtension({ kind: 'k', namespace: 'c', name: 'n', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }), @@ -122,8 +122,8 @@ describe('createExtensionOverrides', () => { createExtension({ namespace: 'a', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 71508b0c15..3615ba29e5 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -17,7 +17,6 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { screen } from '@testing-library/react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import { createFrontendPlugin } from './createFrontendPlugin'; import { JsonObject } from '@backstage/types'; import { createExtension } from './createExtension'; @@ -43,14 +42,14 @@ const Extension1 = createExtension({ const Extension2 = createExtension({ name: '2', attachTo: { id: 'test/output', input: 'names' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('extension-2'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('extension-2') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); @@ -58,45 +57,45 @@ const Extension3 = createExtension({ name: '3', attachTo: { id: 'test/output', input: 'names' }, inputs: { - addons: createExtensionInput({ - name: nameExtensionDataRef, - }), - }, - output: { - name: nameExtensionDataRef, + addons: createExtensionInput([nameExtensionDataRef]), }, + output: [nameExtensionDataRef], factory({ inputs }) { - return { - name: `extension-3:${inputs.addons.map(n => n.output.name).join('-')}`, - }; + return [ + nameExtensionDataRef( + `extension-3:${inputs.addons + .map(n => n.get(nameExtensionDataRef)) + .join('-')}`, + ), + ]; }, }); const Child = createExtension({ name: 'child', attachTo: { id: 'test/3', input: 'addons' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('child'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('child') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); const Child2 = createExtension({ name: 'child2', attachTo: { id: 'test/3', input: 'addons' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('child2'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('child2') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); @@ -104,19 +103,19 @@ const outputExtension = createExtension({ name: 'output', attachTo: { id: 'app', input: 'root' }, inputs: { - names: createExtensionInput({ - name: nameExtensionDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, + names: createExtensionInput([nameExtensionDataRef]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: React.createElement('span', {}, [ - `Names: ${inputs.names.map(n => n.output.name).join(', ')}`, - ]), - }; + return [ + coreExtensionData.reactElement( + React.createElement('span', {}, [ + `Names: ${inputs.names + .map(n => n.get(nameExtensionDataRef)) + .join(', ')}`, + ]), + ), + ]; }, }); diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 0b64490742..3862c0e178 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -19,17 +19,12 @@ export { createExtension, type ExtensionDefinition, type CreateExtensionOptions, - type ExtensionDataValues, type ResolvedExtensionInput, type ResolvedExtensionInputs, - type LegacyCreateExtensionOptions, - type AnyExtensionInputMap, - type AnyExtensionDataMap, } from './createExtension'; export { createExtensionInput, type ExtensionInput, - type LegacyExtensionInput, } from './createExtensionInput'; export { type ExtensionDataContainer } from './createExtensionDataContainer'; export { diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 9cd4a76882..3cbc28f8ff 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -16,9 +16,6 @@ import { AppNode } from '../apis'; import { - AnyExtensionDataMap, - AnyExtensionInputMap, - ExtensionDataValues, ExtensionDefinition, ResolvedExtensionInputs, toInternalExtensionDefinition, @@ -47,13 +44,27 @@ export type InternalExtension = Extension< ( | { readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; - factory(options: { + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; + factory(context: { node: AppNode; config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; } | { readonly version: 'v2'; From 9727678a7bed794d046d5da62598dadd7d8f6a1c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:48:50 +0200 Subject: [PATCH 376/393] frontend-plugin-api: stop exporting createSchemaFromZod Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts | 3 +-- packages/frontend-plugin-api/src/schema/index.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts index 9e63515ae4..85f9d5c44a 100644 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts +++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts @@ -20,8 +20,7 @@ import zodToJsonSchema from 'zod-to-json-schema'; import { PortableSchema } from './types'; /** - * @public - * @deprecated Use the `config.schema` option of `createExtension` instead, or use `createExtensionBlueprint`. + * @internal */ export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, diff --git a/packages/frontend-plugin-api/src/schema/index.ts b/packages/frontend-plugin-api/src/schema/index.ts index 7f21c4e07d..a8f92a37f4 100644 --- a/packages/frontend-plugin-api/src/schema/index.ts +++ b/packages/frontend-plugin-api/src/schema/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export { createSchemaFromZod } from './createSchemaFromZod'; export { type PortableSchema } from './types'; From 613e199390a2dea9b32c221523f7fc1b76dc6322 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:49:35 +0200 Subject: [PATCH 377/393] frontend-plugin-api: remove deprecations from createComponentExtension Signed-off-by: Patrik Oldsberg --- .../extensions/createComponentExtension.tsx | 57 ++++++------------- 1 file changed, 16 insertions(+), 41 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index b83b2586ba..9561636d22 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -15,41 +15,20 @@ */ import { lazy, ComponentType } from 'react'; -import { - AnyExtensionInputMap, - ResolvedExtensionInputs, - createExtension, - createExtensionDataRef, -} from '../wiring'; -import { Expand } from '../types'; -import { PortableSchema } from '../schema'; +import { createExtension, createExtensionDataRef } from '../wiring'; import { ComponentRef } from '../components'; /** @public */ -export function createComponentExtension< - TProps extends {}, - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { +export function createComponentExtension(options: { ref: ComponentRef; name?: string; disabled?: boolean; - /** @deprecated these will be removed in the future */ - inputs?: TInputs; - /** @deprecated these will be removed in the future */ - configSchema?: PortableSchema; loader: | { - lazy: (values: { - config: TConfig; - inputs: Expand>; - }) => Promise>; + lazy: () => Promise>; } | { - sync: (values: { - config: TConfig; - inputs: Expand>; - }) => ComponentType; + sync: () => ComponentType; }; }) { return createExtension({ @@ -57,34 +36,30 @@ export function createComponentExtension< namespace: options.ref.id, name: options.name, attachTo: { id: 'app', input: 'components' }, - inputs: options.inputs, disabled: options.disabled, - configSchema: options.configSchema, - output: { - component: createComponentExtension.componentDataRef, - }, - factory({ config, inputs }) { + output: [createComponentExtension.componentDataRef], + factory() { if ('sync' in options.loader) { - return { - component: { + return [ + createComponentExtension.componentDataRef({ ref: options.ref, - impl: options.loader.sync({ config, inputs }) as ComponentType, - }, - }; + impl: options.loader.sync() as ComponentType, + }), + ]; } const lazyLoader = options.loader.lazy; const ExtensionComponent = lazy(() => - lazyLoader({ config, inputs }).then(Component => ({ + lazyLoader().then(Component => ({ default: Component, })), ) as unknown as ComponentType; - return { - component: { + return [ + createComponentExtension.componentDataRef({ ref: options.ref, impl: ExtensionComponent, - }, - }; + }), + ]; }, }); } From 154573ab641d5f535b1d5944413ad217bf427809 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:50:04 +0200 Subject: [PATCH 378/393] frontend-test-utils: update extension tester to drop v1 support Signed-off-by: Patrik Oldsberg --- .../src/app/createExtensionTester.test.tsx | 22 ++++--- .../src/app/createExtensionTester.tsx | 63 ++++++++----------- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 7467f61c4e..9528562198 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -18,10 +18,10 @@ import React, { useCallback } from 'react'; import { Link } from 'react-router-dom'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { + ApiBlueprint, analyticsApiRef, configApiRef, coreExtensionData, - createApiExtension, createApiFactory, createExtension, createExtensionDataRef, @@ -64,8 +64,8 @@ describe('createExtensionTester', () => { it("should fail to render an extension that doesn't output a react element", async () => { const extension = createExtension({ ...defaultDefinition, - output: { path: coreExtensionData.routePath }, - factory: () => ({ path: '/foo' }), + output: [coreExtensionData.routePath], + factory: () => [coreExtensionData.routePath('/foo')], }); const tester = createExtensionTester(extension); expect(() => tester.render()).toThrowErrorMatchingInlineSnapshot( @@ -122,7 +122,7 @@ describe('createExtensionTester', () => { const appTitle = configApi.getOptionalString('app.title'); return (
    -

    {appTitle ?? 'Backstafe app'}

    +

    {appTitle ?? 'Backstage app'}

    {config.title ?? 'Index page'}

    See details
    @@ -186,12 +186,14 @@ describe('createExtensionTester', () => { // Mocking the analytics api implementation const analyticsApiMock = new MockAnalyticsApi(); - const analyticsApiOverride = createApiExtension({ - factory: createApiFactory({ - api: analyticsApiRef, - deps: {}, - factory: () => analyticsApiMock, - }), + const analyticsApiOverride = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: () => analyticsApiMock, + }), + }, }); const indexPageExtension = createExtension({ diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 1f901699b1..63de46a76e 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -26,13 +26,13 @@ import { ExtensionDataRef, ExtensionDefinition, IconComponent, + NavItemBlueprint, RouteRef, + RouterBlueprint, coreExtensionData, createExtension, createExtensionInput, createExtensionOverrides, - createNavItemExtension, - createRouterExtension, useRouteRef, } from '@backstage/frontend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; @@ -74,30 +74,29 @@ const TestAppNavExtension = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, }); @@ -253,18 +252,7 @@ export class ExtensionTester { let subjectOverride; // attaching to app/routes to render as index route if (subjectInternal.version === 'v1') { - subjectOverride = createExtension({ - ...subjectInternal, - attachTo: { id: 'app/routes', input: 'routes' }, - output: { - ...subjectInternal.output, - path: coreExtensionData.routePath, - }, - factory: params => ({ - ...subjectInternal.factory(params as any), - path: '/', - }), - }); + throw new Error('The extension tester does not support v1 extensions'); } else if (subjectInternal.version === 'v2') { subjectOverride = createExtension({ ...subjectInternal, @@ -282,6 +270,7 @@ export class ExtensionTester { return [...parentOutput, coreExtensionData.routePath('/')]; }, }); + (subjectOverride as any).configSchema = subjectInternal.configSchema; } else { throw new Error('Unsupported extension version'); } @@ -293,11 +282,13 @@ export class ExtensionTester { subjectOverride, ...this.#extensions.slice(1).map(extension => extension.definition), TestAppNavExtension, - createRouterExtension({ + RouterBlueprint.make({ namespace: 'test', - Component: ({ children }) => ( - {children} - ), + params: { + Component: ({ children }) => ( + {children} + ), + }, }), ], }), From ac6a64d7088509e3a76b9686971ad14a1adacc0c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:51:45 +0200 Subject: [PATCH 379/393] app-next: remove usage of v1 extension Signed-off-by: Patrik Oldsberg --- packages/app-next/src/index-public-experimental.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/app-next/src/index-public-experimental.tsx b/packages/app-next/src/index-public-experimental.tsx index 56bc698ffd..27b2af7d91 100644 --- a/packages/app-next/src/index-public-experimental.tsx +++ b/packages/app-next/src/index-public-experimental.tsx @@ -29,12 +29,8 @@ const authRedirectExtension = createExtension({ namespace: 'app', name: 'layout', attachTo: { id: 'app/root', input: 'children' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ - element: , - }), + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement()], }); const app = createApp({ From 90508e3a0661b226da7d906d99a789921339f937 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:54:08 +0200 Subject: [PATCH 380/393] frontend-app-api: update AppNav to use dataRefs from blueprint Signed-off-by: Patrik Oldsberg --- .../frontend-app-api/src/extensions/AppNav.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/frontend-app-api/src/extensions/AppNav.tsx b/packages/frontend-app-api/src/extensions/AppNav.tsx index 49c95e84f2..b80a537ef6 100644 --- a/packages/frontend-app-api/src/extensions/AppNav.tsx +++ b/packages/frontend-app-api/src/extensions/AppNav.tsx @@ -20,8 +20,8 @@ import { coreExtensionData, createExtensionInput, useRouteRef, - createNavItemExtension, - createNavLogoExtension, + NavItemBlueprint, + NavLogoBlueprint, } from '@backstage/frontend-plugin-api'; import { makeStyles } from '@material-ui/core/styles'; import { @@ -53,7 +53,7 @@ const useSidebarLogoStyles = makeStyles({ }); const SidebarLogo = ( - props: (typeof createNavLogoExtension.logoElementsDataRef)['T'], + props: (typeof NavLogoBlueprint.dataRefs.logoElements)['T'], ) => { const classes = useSidebarLogoStyles(); const { isOpen } = useSidebarOpenState(); @@ -70,7 +70,7 @@ const SidebarLogo = ( }; const SidebarNavItem = ( - props: (typeof createNavItemExtension.targetDataRef)['T'], + props: (typeof NavItemBlueprint.dataRefs.target)['T'], ) => { const { icon: Icon, title, routeRef } = props; const link = useRouteRef(routeRef); @@ -86,8 +86,8 @@ export const AppNav = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput([createNavItemExtension.targetDataRef]), - logos: createExtensionInput([createNavLogoExtension.logoElementsDataRef], { + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), + logos: createExtensionInput([NavLogoBlueprint.dataRefs.logoElements], { singleton: true, optional: true, }), @@ -97,12 +97,12 @@ export const AppNav = createExtension({ coreExtensionData.reactElement( {inputs.items.map((item, index) => ( ))} From f010b2cafceebe05a4ad4d8077c9ab3a724fd1de Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:57:04 +0200 Subject: [PATCH 381/393] frontend-app-api: update instantiateAppNodeTree to inline support for v1 extensions Signed-off-by: Patrik Oldsberg --- .../src/tree/instantiateAppNodeTree.test.ts | 459 +++++++++--------- .../src/tree/instantiateAppNodeTree.ts | 33 +- 2 files changed, 262 insertions(+), 230 deletions(-) diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index aded5d7826..b8acb6643d 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -15,14 +15,15 @@ */ import { + AnyExtensionDataRef, AppNode, Extension, ExtensionInput, + PortableSchema, ResolvedExtensionInput, createExtension, createExtensionDataRef, createExtensionInput, - createSchemaFromZod, } from '@backstage/frontend-plugin-api'; import { createAppNodeInstance, @@ -31,7 +32,12 @@ import { import { AppNodeSpec } from '@backstage/frontend-plugin-api'; import { resolveAppTree } from './resolveAppTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +import { + InternalExtension, + resolveExtensionDefinition, +} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { createSchemaFromZod } from '../../../frontend-plugin-api/src/schema/createSchemaFromZod'; import { withLogCollector } from '@backstage/test-utils'; const testDataRef = createExtensionDataRef().with({ id: 'test' }); @@ -80,28 +86,63 @@ function makeInstanceWithId( }; } +function createV1ExtensionInput( + extensionData: Record, + options: { singleton?: boolean; optional?: boolean } = {}, +) { + return { + $$type: '@backstage/ExtensionInput' as const, + extensionData, + config: { + singleton: options.singleton ?? false, + optional: options.optional ?? false, + }, + }; +} + +function createV1Extension(opts: { + namespace: string; + name?: string; + attachTo?: { id: string; input: string }; + inputs?: Record>; + output: Record; + configSchema?: PortableSchema; + factory: (ctx: { inputs: any; config: any }) => any; +}): Extension { + const ext: InternalExtension = { + $$type: '@backstage/Extension', + version: 'v1', + id: opts.name ? `${opts.namespace}/${opts.name}` : opts.namespace, + disabled: false, + attachTo: opts.attachTo ?? { id: 'ignored', input: 'ignored' }, + inputs: opts.inputs ?? {}, + output: opts.output, + configSchema: opts.configSchema, + factory: opts.factory, + }; + return ext; +} + describe('instantiateAppNodeTree', () => { describe('v1', () => { - const simpleExtension = resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - other: otherDataRef.optional(), - }, - configSchema: createSchemaFromZod(z => - z.object({ - output: z.string().default('test'), - other: z.number().optional(), - }), - ), - factory({ config }) { - return { test: config.output, other: config.other }; - }, - }), - ); + const simpleExtension = createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + other: otherDataRef.optional(), + }, + configSchema: createSchemaFromZod(z => + z.object({ + output: z.string().default('test'), + other: z.number().optional(), + }), + ), + factory({ config }) { + return { test: config.output, other: config.other }; + }, + }); it('should instantiate a single node', () => { const tree = resolveAppTree('root-node', [ @@ -129,21 +170,19 @@ describe('instantiateAppNodeTree', () => { it('should instantiate a node with attachments', () => { const tree = resolveAppTree('root-node', [ makeSpec( - resolveExtensionDefinition( - createExtension({ - namespace: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + createV1Extension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createV1ExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), makeSpec(simpleExtension, { id: 'child-node', @@ -175,21 +214,19 @@ describe('instantiateAppNodeTree', () => { const tree = resolveAppTree('root-node', [ { ...makeSpec( - resolveExtensionDefinition( - createExtension({ - namespace: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + createV1Extension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createV1ExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), }, { @@ -262,46 +299,44 @@ describe('instantiateAppNodeTree', () => { const instance = createAppNodeInstance({ attachments, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - optionalSingletonPresent: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - optionalSingletonMissing: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - singleton: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true }, - ), - many: createExtensionInput({ + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + optionalSingletonPresent: createV1ExtensionInput( + { test: testDataRef, other: otherDataRef.optional(), - }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + }, + { singleton: true, optional: true }, + ), + optionalSingletonMissing: createV1ExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true, optional: true }, + ), + singleton: createV1ExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true }, + ), + many: createV1ExtensionInput({ + test: testDataRef, + other: otherDataRef.optional(), + }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), }); @@ -344,19 +379,17 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory() { - const error = new Error('NOPE'); - error.name = 'NopeError'; - throw error; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory() { + const error = new Error('NOPE'); + error.name = 'NopeError'; + throw error; + }, + }), ), attachments: new Map(), }), @@ -369,20 +402,18 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test1: testDataRef, - test2: testDataRef, - }, - factory({}) { - return { test1: 'test', test2: 'test2' }; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test1: testDataRef, + test2: testDataRef, + }, + factory({}) { + return { test1: 'test', test2: 'test2' }; + }, + }), ), attachments: new Map(), }), @@ -395,19 +426,17 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - }, - factory({}) { - return { nonexistent: 'test' } as any; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + }, + factory({}) { + return { nonexistent: 'test' } as any; + }, + }), ), attachments: new Map(), }), @@ -420,23 +449,21 @@ describe('instantiateAppNodeTree', () => { expect(() => createAppNodeInstance({ node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), attachments: new Map(), }), @@ -467,20 +494,18 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'parent', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - declared: createExtensionInput({ - test: testDataRef, - }), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + declared: createV1ExtensionInput({ + test: testDataRef, + }), + }, + output: {}, + factory: () => ({}), + }), ), }), ); @@ -507,15 +532,13 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'parent', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory: () => ({}), + }), ), }), ); @@ -539,23 +562,21 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrow( @@ -576,23 +597,21 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true, optional: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true, optional: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrow( @@ -607,23 +626,21 @@ describe('instantiateAppNodeTree', () => { ['singleton', [makeInstanceWithId(simpleExtension, undefined)]], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - other: otherDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + other: otherDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrowErrorMatchingInlineSnapshot( diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index c70e351105..fd8d324652 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -15,9 +15,7 @@ */ import { - AnyExtensionDataMap, AnyExtensionDataRef, - AnyExtensionInputMap, ExtensionDataContainer, ExtensionDataRef, ExtensionInput, @@ -32,8 +30,10 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; -function resolveInputDataMap( - dataMap: AnyExtensionDataMap, +function resolveV1InputDataMap( + dataMap: { + [name in string]: AnyExtensionDataRef; + }, attachment: AppNode, inputName: string, ) { @@ -134,9 +134,17 @@ function reportUndeclaredAttachments( } function resolveV1Inputs( - inputMap: AnyExtensionInputMap, + inputMap: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }, attachments: ReadonlyMap, -): ResolvedExtensionInputs { +) { return mapValues(inputMap, (input, inputName) => { const attachedNodes = attachments.get(inputName) ?? []; @@ -158,7 +166,7 @@ function resolveV1Inputs( } return { node: attachedNodes[0], - output: resolveInputDataMap( + output: resolveV1InputDataMap( input.extensionData, attachedNodes[0], inputName, @@ -168,9 +176,16 @@ function resolveV1Inputs( return attachedNodes.map(attachment => ({ node: attachment, - output: resolveInputDataMap(input.extensionData, attachment, inputName), + output: resolveV1InputDataMap(input.extensionData, attachment, inputName), })); - }) as ResolvedExtensionInputs; + }) as { + [inputName in string]: { + node: AppNode; + output: { + [name in string]: unknown; + }; + }; + }; } function resolveV2Inputs( From 7fb8102c0cf1bfb6688534ea8a2c66c2a3b29d4e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 14:58:09 +0200 Subject: [PATCH 382/393] frontend-plugin-api: remove deprecated extension creators Signed-off-by: Patrik Oldsberg --- .../src/extensions/createApiExtension.test.ts | 92 ---------- .../src/extensions/createApiExtension.ts | 82 --------- .../createAppRootElementExtension.test.tsx | 109 ----------- .../createAppRootElementExtension.ts | 72 -------- .../createAppRootWrapperExtension.test.tsx | 121 ------------- .../createAppRootWrapperExtension.tsx | 88 --------- .../src/extensions/createNavItemExtension.tsx | 69 ------- .../createNavLogoExtension.test.tsx | 48 ----- .../src/extensions/createNavLogoExtension.tsx | 61 ------- .../extensions/createPageExtension.test.tsx | 145 --------------- .../src/extensions/createPageExtension.tsx | 98 ---------- .../extensions/createRouterExtension.test.tsx | 169 ------------------ .../src/extensions/createRouterExtension.tsx | 88 --------- .../createSignInPageExtension.test.tsx | 47 ----- .../extensions/createSignInPageExtension.tsx | 88 --------- .../src/extensions/createThemeExtension.ts | 44 ----- .../createTranslationExtension.test.ts | 137 -------------- .../extensions/createTranslationExtension.ts | 47 ----- .../src/extensions/index.ts | 10 -- 19 files changed, 1615 deletions(-) delete mode 100644 packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createApiExtension.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createPageExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx delete mode 100644 packages/frontend-plugin-api/src/extensions/createThemeExtension.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts delete mode 100644 packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts deleted file mode 100644 index 8f8a0cd9ac..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createApiExtension } from './createApiExtension'; -import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; - -describe('createApiExtension', () => { - it('fills in the expected values for an existing factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = createApiFactory({ - api, - deps: {}, - factory: () => ({ foo: 'bar' }), - }); - - expect( - createApiExtension({ - factory, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'api', - namespace: 'test', - attachTo: { id: 'app', input: 'apis' }, - disabled: false, - configSchema: undefined, - inputs: {}, - output: { - api: expect.objectContaining({ - $$type: '@backstage/ExtensionDataRef', - id: 'core.api.factory', - config: {}, - }), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); - - it('fills in the expected values for a ref and custom factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = jest.fn(() => ({ foo: 'bar' })); - - const extension = createApiExtension({ - api, - inputs: {}, - factory({ config: _config, inputs: _inputs }) { - return createApiFactory({ - api, - deps: {}, - factory, - }); - }, - }); - // boo - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'api', - namespace: 'test', - attachTo: { id: 'app', input: 'apis' }, - disabled: false, - configSchema: undefined, - inputs: {}, - output: { - api: expect.objectContaining({ - $$type: '@backstage/ExtensionDataRef', - id: 'core.api.factory', - config: {}, - }), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts deleted file mode 100644 index bb854d202f..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; -import { PortableSchema } from '../schema'; -import { ResolvedExtensionInputs, createExtension } from '../wiring'; -import { AnyExtensionInputMap } from '../wiring/createExtension'; -import { Expand } from '../types'; -import { ApiBlueprint } from '../blueprints/ApiBlueprint'; - -/** - * @public - * @deprecated Use {@link ApiBlueprint} instead. - */ -export function createApiExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - api: AnyApiRef; - factory: (options: { - config: TConfig; - inputs: Expand>; - }) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - } - ) & { - configSchema?: PortableSchema; - inputs?: TInputs; - }, -) { - const { factory, configSchema, inputs: extensionInputs } = options; - - const apiRef = - 'api' in options ? options.api : (factory as { api: AnyApiRef }).api; - - return createExtension({ - kind: 'api', - // Since ApiRef IDs use a global namespace we use the namespace here in order to override - // potential plugin IDs and always end up with the format `api:` - namespace: apiRef.id, - attachTo: { id: 'app', input: 'apis' }, - inputs: extensionInputs, - configSchema, - output: { - api: ApiBlueprint.dataRefs.factory, - }, - factory({ config, inputs }) { - if (typeof factory === 'function') { - return { api: factory({ config, inputs }) }; - } - return { api: factory }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link ApiBlueprint} instead. - */ -export namespace createApiExtension { - /** - * @deprecated Use {@link ApiBlueprint} instead. - */ - export const factoryDataRef = ApiBlueprint.dataRefs.factory; -} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx deleted file mode 100644 index 6e80f6fe13..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createAppRootElementExtension } from './createAppRootElementExtension'; - -describe('createAppRootElementExtension', () => { - it('works with simple options and just an element', async () => { - const extension = createAppRootElementExtension({ - element:
    Hello
    , - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-element', - attachTo: { id: 'app/root', input: 'elements' }, - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester(extension).render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - }); - - it('works with complex options and a callback', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createAppRootElementExtension({ - namespace: 'ns', - name: 'test', - configSchema: schema, - attachTo: { id: 'other', input: 'slot' }, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - element: ({ inputs, config }) => ( -
    - Hello, {config.name}, {inputs.children.length} -
    - ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-element', - namespace: 'ns', - name: 'test', - attachTo: { id: 'other', input: 'slot' }, - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester(extension, { config: { name: 'Robin' } }) - .add( - createExtension({ - attachTo: { id: 'app-root-element:ns/test', input: 'children' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
    }), - }), - ) - .render(); - - await expect( - screen.findByText('Hello, Robin, 1'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts deleted file mode 100644 index c8f7bdc366..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { JSX } from 'react'; -import { PortableSchema } from '../schema/types'; -import { Expand } from '../types'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; - -/** - * Creates an extension that renders a React element at the app root, outside of - * the app layout. This is useful for example for shared popups and similar. - * - * @public - * @deprecated Use {@link AppRootElementBlueprint} instead. - */ -export function createAppRootElementExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - element: - | JSX.Element - | ((options: { - inputs: Expand>; - config: TConfig; - }) => JSX.Element); -}): ExtensionDefinition { - return createExtension({ - kind: 'app-root-element', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'elements' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs, config }) { - return { - element: - typeof options.element === 'function' - ? options.element({ inputs, config }) - : options.element, - }; - }, - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx deleted file mode 100644 index 993c4fdee3..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; -import { createPageExtension } from './createPageExtension'; - -describe('createAppRootWrapperExtension', () => { - it('works with simple options and no props', async () => { - const extension = createAppRootWrapperExtension({ - Component: () =>
    Hello
    , - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-wrapper', - attachTo: { id: 'app/root', input: 'wrappers' }, - disabled: false, - inputs: {}, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
    , - }), - ) - .add(extension) - .render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - }); - - it('works with complex options and props', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createAppRootWrapperExtension({ - namespace: 'ns', - name: 'test', - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - Component: ({ inputs, config, children }) => ( -
    - {children} -
    - ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-wrapper', - namespace: 'ns', - name: 'test', - attachTo: { id: 'app/root', input: 'wrappers' }, - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
    Hello
    , - }), - ) - .add(extension, { config: { name: 'Robin' } }) - .add( - createExtension({ - attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
    }), - }), - ) - .render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - await expect(screen.findByTestId('Robin-1')).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx deleted file mode 100644 index f055618ffd..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, PropsWithChildren } from 'react'; -import { PortableSchema } from '../schema/types'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; -import { Expand } from '../types'; -import { AppRootWrapperBlueprint } from '../blueprints/AppRootWrapperBlueprint'; - -/** - * Creates an extension that renders a React wrapper at the app root, enclosing - * the app layout. This is useful for example for adding global React contexts - * and similar. - * - * @public - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ -export function createAppRootWrapperExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition { - return createExtension({ - kind: 'app-root-wrapper', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'wrappers' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - component: AppRootWrapperBlueprint.dataRefs.component, - }, - factory({ inputs, config }) { - const Component = (props: PropsWithChildren<{}>) => { - return ( - - {props.children} - - ); - }; - return { - component: Component, - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ -export namespace createAppRootWrapperExtension { - /** - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ - export const componentDataRef = AppRootWrapperBlueprint.dataRefs.component; -} diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx deleted file mode 100644 index f6da531776..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IconComponent } from '@backstage/core-plugin-api'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { createExtension } from '../wiring'; -import { RouteRef } from '../routing'; -import { NavItemBlueprint } from '../blueprints/NavItemBlueprint'; - -/** - * Helper for creating extensions for a nav item. - * - * @public - * @deprecated Use {@link NavItemBlueprint} instead. - */ -export function createNavItemExtension(options: { - namespace?: string; - name?: string; - routeRef: RouteRef; - title: string; - icon: IconComponent; -}) { - const { routeRef, title, icon, namespace, name } = options; - return createExtension({ - namespace, - name, - kind: 'nav-item', - attachTo: { id: 'app/nav', input: 'items' }, - configSchema: createSchemaFromZod(z => - z.object({ - title: z.string().default(title), - }), - ), - output: { - navTarget: NavItemBlueprint.dataRefs.target, - }, - factory: ({ config }) => ({ - navTarget: { - title: config.title, - icon, - routeRef, - }, - }), - }); -} - -/** - * @public - * @deprecated Use {@link NavItemBlueprint} instead. - */ -export namespace createNavItemExtension { - /** - * @deprecated Use {@link NavItemBlueprint} instead. - */ - export const targetDataRef = NavItemBlueprint.dataRefs.target; -} diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx deleted file mode 100644 index 17187a339d..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { createNavLogoExtension } from './createNavLogoExtension'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), -})); - -describe('createNavLogoExtension', () => { - it('creates the extension properly', () => { - expect( - createNavLogoExtension({ - name: 'test', - logoFull:
    Logo Full
    , - logoIcon:
    Logo Icon
    , - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'nav-logo', - name: 'test', - attachTo: { id: 'app/nav', input: 'logos' }, - disabled: false, - inputs: {}, - output: { - logos: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx deleted file mode 100644 index 449bddb342..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtension } from '../wiring'; -import { NavLogoBlueprint } from '../blueprints/NavLogoBlueprint'; - -/** - * Helper for creating extensions for a nav logos. - * - * @public - * @deprecated Use {@link NavLogoBlueprint} instead. - */ -export function createNavLogoExtension(options: { - name?: string; - namespace?: string; - logoIcon: JSX.Element; - logoFull: JSX.Element; -}) { - const { logoIcon, logoFull } = options; - return createExtension({ - kind: 'nav-logo', - name: options?.name, - namespace: options?.namespace, - attachTo: { id: 'app/nav', input: 'logos' }, - output: { - logos: NavLogoBlueprint.dataRefs.logoElements, - }, - factory: () => { - return { - logos: { - logoIcon, - logoFull, - }, - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link NavLogoBlueprint} instead. - */ -export namespace createNavLogoExtension { - /** - * @deprecated Use {@link NavLogoBlueprint} instead. - */ - export const logoElementsDataRef = NavLogoBlueprint.dataRefs.logoElements; -} diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx deleted file mode 100644 index e5dd4027c0..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { useAnalytics } from '@backstage/core-plugin-api'; -import { waitFor } from '@testing-library/react'; -import { PortableSchema } from '../schema'; -import { coreExtensionData, createExtensionInput } from '../wiring'; -import { createPageExtension } from './createPageExtension'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), - useAnalytics: jest.fn(), -})); - -describe('createPageExtension', () => { - it('creates the extension properly', () => { - const configSchema: PortableSchema<{ path: string }> = { - parse: jest.fn(), - schema: {} as any, - }; - - expect( - createPageExtension({ - name: 'test', - configSchema, - loader: async () =>
    , - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: expect.anything(), - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - expect( - createPageExtension({ - name: 'test', - attachTo: { id: 'other', input: 'place' }, - disabled: true, - configSchema, - inputs: { - first: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - loader: async () =>
    , - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'other', input: 'place' }, - configSchema: expect.anything(), - override: expect.any(Function), - disabled: true, - inputs: { - first: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect( - createPageExtension({ - name: 'test', - defaultPath: '/here', - loader: async () =>
    , - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: expect.anything(), - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); - - it('capture page view event in analytics', async () => { - const captureEvent = jest.fn(); - - (useAnalytics as jest.Mock).mockReturnValue({ - captureEvent, - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
    Component
    , - }), - ).render(); - - await waitFor(() => - expect(captureEvent).toHaveBeenCalledWith( - '_ROUTABLE-EXTENSION-RENDERED', - '', - ), - ); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx deleted file mode 100644 index bef642e465..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { ExtensionBoundary } from '../components'; -import { createSchemaFromZod, PortableSchema } from '../schema'; -import { - coreExtensionData, - createExtension, - ResolvedExtensionInputs, - AnyExtensionInputMap, -} from '../wiring'; -import { RouteRef } from '../routing'; -import { Expand } from '../types'; -import { ExtensionDefinition } from '../wiring/createExtension'; - -/** - * Helper for creating extensions for a routable React page component. - * - * @public - * @deprecated Use {@link PageBlueprint} instead. - */ -export function createPageExtension< - TConfig extends { path: string }, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -): ExtensionDefinition { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ path: z.string().default(options.defaultPath) }), - ) as PortableSchema); - - return createExtension({ - kind: 'page', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' }, - configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - }, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - routeRef: options.routeRef, - element: ( - - - - ), - }; - }, - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx deleted file mode 100644 index 2c16076fcf..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createSpecializedApp } from '@backstage/frontend-app-api'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { MockConfigApi } from '@backstage/test-utils'; -import { MemoryRouter } from 'react-router-dom'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createExtensionOverrides } from '../wiring/createExtensionOverrides'; -import { createPageExtension } from './createPageExtension'; -import { createRouterExtension } from './createRouterExtension'; - -describe('createRouterExtension', () => { - it('works with simple options and no props', async () => { - const extension = createRouterExtension({ - namespace: 'test', - Component: ({ children }) => ( - -
    {children}
    -
    - ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-router-component', - namespace: 'test', - attachTo: { id: 'app/root', input: 'router' }, - disabled: false, - inputs: {}, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createPageExtension({ - namespace: 'test', - defaultPath: '/', - loader: async () =>
    , - }), - ], - }), - ], - }); - - render(app.createRoot()); - - await expect( - screen.findByTestId('test-contents'), - ).resolves.toBeInTheDocument(); - await expect( - screen.findByTestId('test-router'), - ).resolves.toBeInTheDocument(); - }); - - it('works with complex options and props', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createRouterExtension({ - namespace: 'test', - name: 'test', - configSchema: schema, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - Component: ({ inputs, config, children }) => ( - -
    - {children} -
    -
    - ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-router-component', - namespace: 'test', - name: 'test', - attachTo: { id: 'app/root', input: 'router' }, - configSchema: schema, - disabled: false, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createExtension({ - namespace: 'test', - attachTo: { - id: 'app-router-component:test/test', - input: 'children', - }, - output: { element: coreExtensionData.reactElement }, // doesn't matter - factory: () => ({ element:
    }), - }), - createPageExtension({ - namespace: 'test', - defaultPath: '/', - loader: async () =>
    , - }), - ], - }), - ], - config: new MockConfigApi({ - app: { - extensions: [ - { - 'app-router-component:test/test': { config: { name: 'Robin' } }, - }, - ], - }, - }), - }); - - render(app.createRoot()); - - await expect( - screen.findByTestId('test-contents'), - ).resolves.toBeInTheDocument(); - await expect( - screen.findByTestId('test-router-Robin-1'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx deleted file mode 100644 index 258940c694..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, PropsWithChildren } from 'react'; -import { PortableSchema } from '../schema/types'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; -import { Expand } from '../types'; -import { RouterBlueprint } from '../blueprints/RouterBlueprint'; - -/** - * Creates an extension that replaces the router implementation at the app root. - * This is useful to be able to for example replace the BrowserRouter with a - * MemoryRouter in tests, or to add additional props to a BrowserRouter. - * - * @public - * @deprecated Use {@link RouterBlueprint} instead. - */ -export function createRouterExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition { - return createExtension({ - kind: 'app-router-component', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'router' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - component: RouterBlueprint.dataRefs.component, - }, - factory({ inputs, config }) { - const Component = (props: PropsWithChildren<{}>) => { - return ( - - {props.children} - - ); - }; - return { - component: Component, - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link RouterBlueprint} instead. - */ -export namespace createRouterExtension { - /** - * @deprecated Use {@link RouterBlueprint} instead. - */ - export const componentDataRef = RouterBlueprint.dataRefs.component; -} diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx deleted file mode 100644 index d8835ae788..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import { createSignInPageExtension } from './createSignInPageExtension'; -import { coreExtensionData, createExtension } from '../wiring'; - -describe('createSignInPageExtension', () => { - it('renders a sign-in page', async () => { - const SignInPage = createSignInPageExtension({ - name: 'test', - loader: async () => () =>
    , - }); - - createExtensionTester( - createExtension({ - name: 'dummy', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element:
    }), - }), - ) - .add(SignInPage) - .render(); - - await expect( - screen.findByTestId('sign-in-page'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx deleted file mode 100644 index 9c663b72f5..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, lazy } from 'react'; -import { ExtensionBoundary } from '../components'; -import { PortableSchema } from '../schema'; -import { - createExtension, - ResolvedExtensionInputs, - AnyExtensionInputMap, - ExtensionDefinition, -} from '../wiring'; -import { Expand } from '../types'; -import { SignInPageProps } from '@backstage/core-plugin-api'; -import { SignInPageBlueprint } from '../blueprints'; - -/** - * - * @public - * @deprecated Use {@link SignInPageBlueprint} instead. - */ -export function createSignInPageExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise>; -}): ExtensionDefinition { - return createExtension({ - kind: 'sign-in-page', - namespace: options?.namespace, - name: options?.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'signInPage' }, - configSchema: options.configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: { - component: createSignInPageExtension.componentDataRef, - }, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(component => ({ default: component })), - ); - - return { - component: props => ( - - - - ), - }; - }, - }); -} - -/** - * @public - * @deprecated Use {@link SignInPageBlueprint} instead. - */ -export namespace createSignInPageExtension { - /** - * @deprecated Use {@link SignInPageBlueprint} instead. - */ - export const componentDataRef = SignInPageBlueprint.dataRefs.component; -} diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts deleted file mode 100644 index 2662ebb39c..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ThemeBlueprint } from '../blueprints/ThemeBlueprint'; -import { createExtension } from '../wiring'; -import { AppTheme } from '@backstage/core-plugin-api'; - -/** - * @public - * @deprecated Use {@link ThemeBlueprint} instead. - */ -export function createThemeExtension(theme: AppTheme) { - return createExtension({ - kind: 'theme', - namespace: 'app', - name: theme.id, - attachTo: { id: 'app', input: 'themes' }, - output: { - theme: ThemeBlueprint.dataRefs.theme, - }, - factory: () => ({ theme }), - }); -} - -/** - * @public - * @deprecated Use {@link ThemeBlueprint} instead. - */ -export namespace createThemeExtension { - export const themeDataRef = ThemeBlueprint.dataRefs.theme; -} diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts deleted file mode 100644 index 019c565cbd..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createTranslationRef, - createTranslationMessages, - createTranslationResource, -} from '@backstage/core-plugin-api/alpha'; -import { createTranslationExtension } from './createTranslationExtension'; - -const translationRef = createTranslationRef({ - id: 'test', - messages: { - a: 'a', - b: 'b', - }, -}); - -describe('createTranslationExtension', () => { - it('creates a translation message extension', () => { - const messages = createTranslationMessages({ - ref: translationRef, - messages: { - a: 'A', - }, - }); - const extension = createTranslationExtension({ resource: messages }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - override: expect.any(Function), - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect((extension as any).factory({} as any)).toEqual({ - resource: messages, - }); - }); - - it('creates a translation resource extension', () => { - const resource = createTranslationResource({ - ref: translationRef, - translations: { - sv: () => - Promise.resolve({ - default: createTranslationMessages({ - ref: translationRef, - messages: { - a: 'Ä', - b: 'Ö', - }, - }), - }), - }, - }); - const extension = createTranslationExtension({ resource }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - override: expect.any(Function), - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect((extension as any).factory({} as any)).toEqual({ resource }); - }); - - it('creates a translation resource extension with a name', () => { - expect( - createTranslationExtension({ - name: 'sv', - resource: createTranslationResource({ - ref: translationRef, - translations: { - sv: () => - Promise.resolve({ - default: createTranslationMessages({ - ref: translationRef, - messages: { - a: 'Ä', - b: 'Ö', - }, - }), - }), - }, - }), - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - override: expect.any(Function), - name: 'sv', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts deleted file mode 100644 index 8a094cd593..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TranslationBlueprint } from '../blueprints/TranslationBlueprint'; -import { TranslationMessages, TranslationResource } from '../translation'; -import { createExtension } from '../wiring'; - -/** - * @public - * @deprecated Use {@link TranslationBlueprint} instead. - */ -export function createTranslationExtension(options: { - name?: string; - resource: TranslationResource | TranslationMessages; -}) { - return createExtension({ - kind: 'translation', - namespace: options.resource.id, - name: options.name, - attachTo: { id: 'app', input: 'translations' }, - output: { - resource: TranslationBlueprint.dataRefs.translation, - }, - factory: () => ({ resource: options.resource }), - }); -} - -/** - * @public - * @deprecated Use {@link TranslationBlueprint} instead. - */ -export namespace createTranslationExtension { - export const translationDataRef = TranslationBlueprint.dataRefs.translation; -} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 562cb728cb..f75c7474ac 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -14,14 +14,4 @@ * limitations under the License. */ -export { createApiExtension } from './createApiExtension'; -export { createAppRootElementExtension } from './createAppRootElementExtension'; -export { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; -export { createRouterExtension } from './createRouterExtension'; -export { createPageExtension } from './createPageExtension'; -export { createNavItemExtension } from './createNavItemExtension'; -export { createNavLogoExtension } from './createNavLogoExtension'; -export { createSignInPageExtension } from './createSignInPageExtension'; -export { createThemeExtension } from './createThemeExtension'; export { createComponentExtension } from './createComponentExtension'; -export { createTranslationExtension } from './createTranslationExtension'; From 44ea4b9571f8248fb00a7da95c7060d805b5ac22 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:02:21 +0200 Subject: [PATCH 383/393] frontend-test-utils: update to reference dataRefs via blueprint Signed-off-by: Patrik Oldsberg --- packages/frontend-test-utils/src/app/renderInTestApp.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index b37a7c2a8d..8bbe2d058c 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -29,8 +29,8 @@ import { useRouteRef, createExtensionInput, IconComponent, - createNavItemExtension, RouterBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; /** @@ -86,7 +86,7 @@ export const TestAppNavExtension = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput([createNavItemExtension.targetDataRef]), + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), }, output: [coreExtensionData.reactElement], factory({ inputs }) { @@ -96,7 +96,7 @@ export const TestAppNavExtension = createExtension({
      {inputs.items.map((item, index) => { const { icon, title, routeRef } = item.get( - createNavItemExtension.targetDataRef, + NavItemBlueprint.dataRefs.target, ); return ( From bf92da98bc14a6cb963e49ec60db41f6810dad16 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:11:35 +0200 Subject: [PATCH 384/393] catalog-react: remove deprecated extension creators Signed-off-by: Patrik Oldsberg --- .../alpha/blueprints/EntityCardBlueprint.ts | 23 +- .../blueprints/EntityContentBlueprint.ts | 30 +-- .../src/alpha/blueprints/extensionData.tsx | 34 +++ .../catalog-react/src/alpha/extensions.tsx | 213 ------------------ plugins/catalog-react/src/alpha/index.ts | 4 +- 5 files changed, 67 insertions(+), 237 deletions(-) create mode 100644 plugins/catalog-react/src/alpha/blueprints/extensionData.tsx delete mode 100644 plugins/catalog-react/src/alpha/extensions.tsx diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts index d13fd2f2c0..82ff2df44a 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts @@ -19,7 +19,10 @@ import { coreExtensionData, createExtensionBlueprint, } from '@backstage/frontend-plugin-api'; -import { catalogExtensionData } from '../extensions'; +import { + entityFilterFunctionDataRef, + entityFilterExpressionDataRef, +} from './extensionData'; /** * @alpha @@ -30,12 +33,12 @@ export const EntityCardBlueprint = createExtensionBlueprint({ attachTo: { id: 'entity-content:catalog/overview', input: 'cards' }, output: [ coreExtensionData.reactElement, - catalogExtensionData.entityFilterFunction.optional(), - catalogExtensionData.entityFilterExpression.optional(), + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), ], dataRefs: { - filterFunction: catalogExtensionData.entityFilterFunction, - filterExpression: catalogExtensionData.entityFilterExpression, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, }, config: { schema: { @@ -49,19 +52,19 @@ export const EntityCardBlueprint = createExtensionBlueprint({ }: { loader: () => Promise; filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; + | typeof entityFilterFunctionDataRef.T + | typeof entityFilterExpressionDataRef.T; }, { node, config }, ) { yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); if (config.filter) { - yield catalogExtensionData.entityFilterExpression(config.filter); + yield entityFilterExpressionDataRef(config.filter); } else if (typeof filter === 'string') { - yield catalogExtensionData.entityFilterExpression(filter); + yield entityFilterExpressionDataRef(filter); } else if (typeof filter === 'function') { - yield catalogExtensionData.entityFilterFunction(filter); + yield entityFilterFunctionDataRef(filter); } }, }); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts index 36bca99ecc..54cd476a90 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts @@ -20,7 +20,11 @@ import { ExtensionBoundary, RouteRef, } from '@backstage/frontend-plugin-api'; -import { catalogExtensionData } from '../extensions'; +import { + entityContentTitleDataRef, + entityFilterFunctionDataRef, + entityFilterExpressionDataRef, +} from './extensionData'; /** * @alpha @@ -32,15 +36,15 @@ export const EntityContentBlueprint = createExtensionBlueprint({ output: [ coreExtensionData.reactElement, coreExtensionData.routePath, - catalogExtensionData.entityContentTitle, + entityContentTitleDataRef, coreExtensionData.routeRef.optional(), - catalogExtensionData.entityFilterFunction.optional(), - catalogExtensionData.entityFilterExpression.optional(), + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), ], dataRefs: { - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction, - filterExpression: catalogExtensionData.entityFilterExpression, + title: entityContentTitleDataRef, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, }, config: { schema: { @@ -62,8 +66,8 @@ export const EntityContentBlueprint = createExtensionBlueprint({ defaultTitle: string; routeRef?: RouteRef; filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; + | typeof entityFilterFunctionDataRef.T + | typeof entityFilterExpressionDataRef.T; }, { node, config }, ) { @@ -74,18 +78,18 @@ export const EntityContentBlueprint = createExtensionBlueprint({ yield coreExtensionData.routePath(path); - yield catalogExtensionData.entityContentTitle(title); + yield entityContentTitleDataRef(title); if (routeRef) { yield coreExtensionData.routeRef(routeRef); } if (config.filter) { - yield catalogExtensionData.entityFilterExpression(config.filter); + yield entityFilterExpressionDataRef(config.filter); } else if (typeof filter === 'string') { - yield catalogExtensionData.entityFilterExpression(filter); + yield entityFilterExpressionDataRef(filter); } else if (typeof filter === 'function') { - yield catalogExtensionData.entityFilterFunction(filter); + yield entityFilterFunctionDataRef(filter); } }, }); diff --git a/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx new file mode 100644 index 0000000000..09aac809cb --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; + +/** @internal */ +export const entityContentTitleDataRef = createExtensionDataRef().with({ + id: 'catalog.entity-content-title', +}); + +/** @internal */ +export const entityFilterFunctionDataRef = createExtensionDataRef< + (entity: Entity) => boolean +>().with({ id: 'catalog.entity-filter-function' }); + +/** @internal */ +export const entityFilterExpressionDataRef = + createExtensionDataRef().with({ + id: 'catalog.entity-filter-expression', + }); diff --git a/plugins/catalog-react/src/alpha/extensions.tsx b/plugins/catalog-react/src/alpha/extensions.tsx deleted file mode 100644 index e527edc355..0000000000 --- a/plugins/catalog-react/src/alpha/extensions.tsx +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AnyExtensionInputMap, - ExtensionBoundary, - PortableSchema, - ResolvedExtensionInputs, - RouteRef, - coreExtensionData, - createExtension, - createExtensionDataRef, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import React, { lazy } from 'react'; -import { Entity } from '@backstage/catalog-model'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { Expand } from '../../../../packages/frontend-plugin-api/src/types'; - -export { useEntityPermission } from '../hooks/useEntityPermission'; -export { isOwnerOf } from '../utils'; -export * from '../translation'; - -/** - * @alpha - * @deprecated use `dataRefs` property on either {@link EntityCardBlueprint} or {@link EntityContentBlueprint} instead - */ -export const catalogExtensionData = { - entityContentTitle: createExtensionDataRef().with({ - id: 'catalog.entity-content-title', - }), - entityFilterFunction: createExtensionDataRef< - (entity: Entity) => boolean - >().with({ id: 'catalog.entity-filter-function' }), - entityFilterExpression: createExtensionDataRef().with({ - id: 'catalog.entity-filter-expression', - }), -}; - -// TODO: Figure out how to merge with provided config schema -/** - * @alpha - * @deprecated use {@link EntityCardBlueprint} instead - */ -export function createEntityCardExtension< - TConfig extends { filter?: string }, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; -}) { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ - filter: z.string().optional(), - }), - ) as PortableSchema); - return createExtension({ - kind: 'entity-card', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'entity-content:catalog/overview', - input: 'cards', - }, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - inputs: options.inputs, - configSchema, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ inputs, config }) - .then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - ...mergeFilters({ config, options }), - }; - }, - }); -} - -/** - * @alpha - * @deprecated use {@link EntityContentBlueprint} instead - */ -export function createEntityContentExtension< - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - defaultPath: string; - defaultTitle: string; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - inputs: Expand>; - }) => Promise; -}) { - return createExtension({ - kind: 'entity-content', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'page:catalog/entity', - input: 'contents', - }, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - inputs: options.inputs, - configSchema: createSchemaFromZod(z => - z.object({ - path: z.string().default(options.defaultPath), - title: z.string().default(options.defaultTitle), - filter: z.string().optional(), - }), - ), - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - title: config.title, - routeRef: options.routeRef, - element: ( - - - - ), - ...mergeFilters({ config, options }), - }; - }, - }); -} - -/** - * Decides what filter outputs to produce, given some options and config - */ -function mergeFilters(inputs: { - options: { - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - }; - config: { - filter?: string; - }; -}): { - filterFunction?: typeof catalogExtensionData.entityFilterFunction.T; - filterExpression?: typeof catalogExtensionData.entityFilterExpression.T; -} { - const { options, config } = inputs; - if (config.filter) { - return { filterExpression: config.filter }; - } else if (typeof options.filter === 'string') { - return { filterExpression: options.filter }; - } else if (typeof options.filter === 'function') { - return { filterFunction: options.filter }; - } - return {}; -} diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts index 3930fbde87..a46fec0615 100644 --- a/plugins/catalog-react/src/alpha/index.ts +++ b/plugins/catalog-react/src/alpha/index.ts @@ -15,5 +15,7 @@ */ export * from './blueprints'; -export * from './extensions'; export * from './converters'; +export { catalogReactTranslationRef } from '../translation'; +export { isOwnerOf } from '../utils/isOwnerOf'; +export { useEntityPermission } from '../hooks/useEntityPermission'; From 71bf340bee44b06d31543c7a8c825f457cf1ac41 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:12:18 +0200 Subject: [PATCH 385/393] catalog: remove deprecated extension creator Signed-off-by: Patrik Oldsberg --- .../alpha/createCatalogFilterExtension.tsx | 66 ------------------- plugins/catalog/src/alpha/index.ts | 1 - 2 files changed, 67 deletions(-) delete mode 100644 plugins/catalog/src/alpha/createCatalogFilterExtension.tsx diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx deleted file mode 100644 index 85d911ef3b..0000000000 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { - AnyExtensionInputMap, - ExtensionBoundary, - PortableSchema, - coreExtensionData, - createExtension, -} from '@backstage/frontend-plugin-api'; - -/** - * @alpha - * @deprecated Use {@link CatalogFilterBlueprint} instead - */ -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}) { - return createExtension({ - kind: 'catalog-filter', - namespace: options.namespace, - name: options.name, - attachTo: { id: 'page:catalog', input: 'filters' }, - inputs: options.inputs ?? {}, - configSchema: options.configSchema, - output: { - element: coreExtensionData.reactElement, - }, - factory({ config, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config }) - .then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - }; - }, - }); -} diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index f004c4f19e..3357024fd9 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -15,7 +15,6 @@ */ export { default } from './plugin'; -export { createCatalogFilterExtension } from './createCatalogFilterExtension'; export * from './blueprints'; export * from './translation'; From d0501bb8c267fdbc871b0deeb197df6141a6794e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:13:38 +0200 Subject: [PATCH 386/393] search-react: removed deprecated extension creators Signed-off-by: Patrik Oldsberg --- .../src/alpha/extensions.test.tsx | 171 ------------------ plugins/search-react/src/alpha/extensions.tsx | 133 -------------- plugins/search-react/src/alpha/index.ts | 1 - 3 files changed, 305 deletions(-) delete mode 100644 plugins/search-react/src/alpha/extensions.test.tsx delete mode 100644 plugins/search-react/src/alpha/extensions.tsx diff --git a/plugins/search-react/src/alpha/extensions.test.tsx b/plugins/search-react/src/alpha/extensions.test.tsx deleted file mode 100644 index 5194fadd1c..0000000000 --- a/plugins/search-react/src/alpha/extensions.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createExtensionInput, - createPageExtension, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { SearchResult } from '@backstage/plugin-search-common'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSearchResultListItemExtension } from './extensions'; -import { BaseSearchResultListItemProps } from './blueprints'; - -describe('createSearchResultListItemExtension', () => { - it('Should use the correct result component', async () => { - type TechDocsSearchResultListItemProps = BaseSearchResultListItemProps<{ - lineClamp: number; - }>; - const TechDocsSearchResultItemComponent = ( - props: TechDocsSearchResultListItemProps, - ) => ( -
      - TechDocs - Rank: {props.rank} - Line clamp: {props.lineClamp} -
      - ); - - const TechDocsSearchResultItemExtension = - createSearchResultListItemExtension({ - namespace: 'techdocs', - configSchema: createSchemaFromZod(z => - z - .object({ - noTrack: z.boolean().default(true), - lineClamp: z.number().default(5), - }) - .default({}), - ), - predicate: result => result.type === 'techdocs', - component: - async ({ config }) => - props => - , - }); - - const ExploreSearchResultItemComponent = ( - props: BaseSearchResultListItemProps, - ) =>
      Explore - Rank: {props.rank}
      ; - - const ExploreSearchResultItemExtension = - createSearchResultListItemExtension({ - namespace: 'explore', - predicate: result => result.type === 'explore', - component: async () => ExploreSearchResultItemComponent, - }); - - const SearchPageExtension = createPageExtension({ - namespace: 'search', - defaultPath: '/', - inputs: { - items: createExtensionInput({ - item: createSearchResultListItemExtension.itemDataRef, - }), - }, - loader: async ({ inputs }) => { - const results = [ - { - type: 'techdocs', - rank: 1, - document: { - title: 'Title1', - text: 'Text1', - location: '/location1', - }, - }, - { - type: 'explore', - rank: 2, - document: { - title: 'Title2', - text: 'Text2', - location: '/location2', - }, - }, - { - type: 'other', - rank: 3, - document: { - title: 'Title3', - text: 'Text3', - location: '/location3', - }, - }, - ]; - - const DefaultResultItem = (props: BaseSearchResultListItemProps) => ( -
      Default - Rank: {props.rank}
      - ); - - const getResultItemComponent = (result: SearchResult) => { - const value = inputs.items.find(item => - item?.output.item.predicate?.(result), - ); - return value?.output.item.component ?? DefaultResultItem; - }; - - const Component = () => { - return ( -
      -

      Search Page

      -
        - {results.map((result, index) => { - const SearchResultListItem = getResultItemComponent(result); - return ( - - ); - })} -
      -
      - ); - }; - - return ; - }, - }); - - createExtensionTester(SearchPageExtension) - .add(TechDocsSearchResultItemExtension, { - // TODO(Rugvip): We need to make the config input type available for use here - config: { - lineClamp: 3, - } as any, - }) - .add(ExploreSearchResultItemExtension) - .render(); - - expect(await screen.findByText(/Search Page/)).toBeInTheDocument(); - - expect( - await screen.findByText(/TechDocs - Rank: 1 - Line clamp: 3/, { - exact: false, - }), - ).toBeInTheDocument(); - - expect( - await screen.findByText(/Explore - Rank: 2/, { exact: false }), - ).toBeInTheDocument(); - - expect( - await screen.findByText(/Default - Rank: 3/, { exact: false }), - ).toBeInTheDocument(); - }); -}); diff --git a/plugins/search-react/src/alpha/extensions.tsx b/plugins/search-react/src/alpha/extensions.tsx deleted file mode 100644 index db09510a1e..0000000000 --- a/plugins/search-react/src/alpha/extensions.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { - ExtensionBoundary, - PortableSchema, - createExtension, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import { - SearchResultItemExtensionComponent, - SearchResultItemExtensionPredicate, -} from './blueprints'; -import { SearchResultListItemExtension } from '../extensions'; -import { searchResultListItemDataRef } from './blueprints/types'; - -/** - * @alpha - * @deprecated Use {@link SearchResultListItemBlueprint} instead - */ -export type SearchResultItemExtensionOptions< - TConfig extends { noTrack?: boolean }, -> = { - /** - * The extension namespace. - */ - namespace?: string; - /** - * The extension name. - */ - name?: string; - /** - * The extension attachment point (e.g., search modal or page). - */ - attachTo?: { id: string; input: string }; - /** - * Optional extension config schema. - */ - configSchema?: PortableSchema; - /** - * The extension component. - */ - component: (options: { - config: TConfig; - }) => Promise; - /** - * When an extension defines a predicate, it returns true if the result should be rendered by that extension. - * Defaults to a predicate that returns true, which means it renders all sorts of results. - */ - predicate?: SearchResultItemExtensionPredicate; -}; - -/** - * Creates items for the search result list. - * - * @alpha - * @deprecated Use {@link SearchResultListItemBlueprint} instead - */ -export function createSearchResultListItemExtension< - TConfig extends { noTrack?: boolean }, ->(options: SearchResultItemExtensionOptions) { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ - noTrack: z.boolean().default(false), - }), - ) as PortableSchema); - - return createExtension({ - kind: 'search-result-list-item', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'page:search', - input: 'items', - }, - configSchema, - output: { - item: createSearchResultListItemExtension.itemDataRef, - }, - factory({ config, node }) { - const ExtensionComponent = lazy(() => - options - .component({ config }) - .then(component => ({ default: component })), - ) as unknown as SearchResultItemExtensionComponent; - - return { - item: { - predicate: options.predicate, - component: props => ( - - - - - - ), - }, - }; - }, - }); -} - -/** - * @alpha - * @deprecated Use {@link SearchResultListItemBlueprint} instead - */ -export namespace createSearchResultListItemExtension { - /** - * @deprecated Use {@link SearchResultListItemBlueprint#dataRefs.item} instead - */ - export const itemDataRef = searchResultListItemDataRef; -} diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts index 04026d629d..239ffa89dd 100644 --- a/plugins/search-react/src/alpha/index.ts +++ b/plugins/search-react/src/alpha/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './extensions'; export * from './blueprints'; From 28311eb02fce0793dded14179b11bd31e3c35fc0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:15:29 +0200 Subject: [PATCH 387/393] update API reports for v1 extension removal Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/api-report.md | 496 ++------------------- plugins/catalog-react/api-report-alpha.md | 98 ---- plugins/catalog/api-report-alpha.md | 24 - plugins/search-react/api-report-alpha.md | 53 --- 4 files changed, 30 insertions(+), 641 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index a6ca3f3aff..384c14fb17 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -89,8 +89,6 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; -import { ZodSchema } from 'zod'; -import { ZodTypeDef } from 'zod'; export { AlertApi }; @@ -147,11 +145,6 @@ export { AnyApiFactory }; export { AnyApiRef }; -// @public @deprecated (undocumented) -export type AnyExtensionDataMap = { - [name in string]: AnyExtensionDataRef; -}; - // @public (undocumented) export type AnyExtensionDataRef = ExtensionDataRef< unknown, @@ -161,17 +154,6 @@ export type AnyExtensionDataRef = ExtensionDataRef< } >; -// @public @deprecated (undocumented) -export type AnyExtensionInputMap = { - [inputName in string]: LegacyExtensionInput< - AnyExtensionDataMap, - { - optional: boolean; - singleton: boolean; - } - >; -}; - // @public (undocumented) export type AnyExternalRoutes = { [name in string]: ExternalRouteRef; @@ -456,141 +438,50 @@ export type CoreNotFoundErrorPageProps = { // @public (undocumented) export type CoreProgressProps = {}; -// @public @deprecated (undocumented) -export function createApiExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - api: AnyApiRef; - factory: (options: { - config: TConfig; - inputs: Expand>; - }) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - } - ) & { - configSchema?: PortableSchema; - inputs?: TInputs; - }, -): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createApiExtension { - const // @deprecated (undocumented) - factoryDataRef: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; -} - export { createApiFactory }; export { createApiRef }; -// @public @deprecated -export function createAppRootElementExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - element: - | JSX_2.Element - | ((options: { - inputs: Expand>; - config: TConfig; - }) => JSX_2.Element); -}): ExtensionDefinition; - -// @public @deprecated -export function createAppRootWrapperExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition; - -// @public @deprecated (undocumented) -export namespace createAppRootWrapperExtension { - const // @deprecated (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType<{ - children?: React_2.ReactNode; - }>, - 'app.root.wrapper', - {} - >; -} - // @public (undocumented) -export function createComponentExtension< - TProps extends {}, - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { +export function createComponentExtension(options: { ref: ComponentRef; name?: string; disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; loader: | { - lazy: (values: { - config: TConfig; - inputs: Expand>; - }) => Promise>; + lazy: () => Promise>; } | { - sync: (values: { - config: TConfig; - inputs: Expand>; - }) => ComponentType; + sync: () => ComponentType; }; }): ExtensionDefinition< - TConfig, - TConfig, - never, - never, { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; + [x: string]: any; + }, + { + [x: string]: any; + }, + ConfigurableExtensionDataRef< + { + ref: ComponentRef; + impl: ComponentType; + }, + 'core.component.component', + {} + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'component'; + namespace: string; + name: string; } >; @@ -659,21 +550,6 @@ export function createExtension< } >; -// @public @deprecated (undocumented) -export function createExtension< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, ->( - options: LegacyCreateExtensionOptions< - TOutput, - TInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition; - // @public export function createExtensionBlueprint< TParams, @@ -795,24 +671,6 @@ export function createExtensionDataRef(): { }): ConfigurableExtensionDataRef; }; -// @public @deprecated (undocumented) -export function createExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { - singleton?: boolean; - optional?: boolean; - }, ->( - extensionData: TExtensionDataMap, - config?: TConfig, -): LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } ->; - // @public (undocumented) export function createExtensionInput< UExtensionData extends ExtensionDataRef< @@ -926,105 +784,6 @@ export function createFrontendPlugin< } >; -// @public @deprecated -export function createNavItemExtension(options: { - namespace?: string; - name?: string; - routeRef: RouteRef; - title: string; - icon: IconComponent_2; -}): ExtensionDefinition< - { - title: string; - }, - { - title?: string | undefined; - }, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createNavItemExtension { - const // @deprecated (undocumented) - targetDataRef: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent_2; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; -} - -// @public @deprecated -export function createNavLogoExtension(options: { - name?: string; - namespace?: string; - logoIcon: JSX.Element; - logoFull: JSX.Element; -}): ExtensionDefinition< - unknown, - unknown, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createNavLogoExtension { - const // @deprecated (undocumented) - logoElementsDataRef: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element | undefined; - logoFull?: JSX.Element | undefined; - }, - 'core.nav-logo.logo-elements', - {} - >; -} - -// @public @deprecated -export function createPageExtension< - TConfig extends { - path: string; - }, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -): ExtensionDefinition; - // @public @deprecated (undocumented) export const createPlugin: typeof createFrontendPlugin; @@ -1048,75 +807,6 @@ export function createRouteRef< } >; -// @public @deprecated -export function createRouterExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition; - -// @public @deprecated (undocumented) -export namespace createRouterExtension { - const // @deprecated (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType<{ - children?: React_2.ReactNode; - }>, - 'app.router.wrapper', - {} - >; -} - -// @public @deprecated (undocumented) -export function createSchemaFromZod( - schemaCreator: (zImpl: typeof z) => ZodSchema, -): PortableSchema; - -// @public @deprecated (undocumented) -export function createSignInPageExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise>; -}): ExtensionDefinition; - -// @public @deprecated (undocumented) -export namespace createSignInPageExtension { - const // @deprecated (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType, - 'core.sign-in-page.component', - {} - >; -} - // @public export function createSubRouteRef< Path extends string, @@ -1126,62 +816,6 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; -// @public @deprecated (undocumented) -export function createThemeExtension(theme: AppTheme): ExtensionDefinition< - unknown, - unknown, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createThemeExtension { - const // (undocumented) - themeDataRef: ConfigurableExtensionDataRef< - AppTheme, - 'core.theme.theme', - {} - >; -} - -// @public @deprecated (undocumented) -export function createTranslationExtension(options: { - name?: string; - resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition< - unknown, - unknown, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @public @deprecated (undocumented) -export namespace createTranslationExtension { - const // (undocumented) - translationDataRef: ConfigurableExtensionDataRef< - | TranslationResource - | TranslationMessages< - string, - { - [x: string]: string; - }, - boolean - >, - 'core.translation.translation', - {} - >; -} - export { createTranslationMessages }; export { createTranslationRef }; @@ -1429,21 +1063,6 @@ export type ExtensionDataValue = { readonly value: TData; }; -// @public @deprecated -export type ExtensionDataValues = { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? never - : DataName]: TExtensionData[DataName]['T']; -} & { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? DataName - : never]?: TExtensionData[DataName]['T']; -}; - // @public (undocumented) export interface ExtensionDefinition< TConfig, @@ -1696,56 +1315,6 @@ export { IdentityApi }; export { identityApiRef }; -// @public @deprecated (undocumented) -export interface LegacyCreateExtensionOptions< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, -> { - // (undocumented) - attachTo: { - id: string; - input: string; - }; - // (undocumented) - configSchema?: PortableSchema; - // (undocumented) - disabled?: boolean; - // (undocumented) - factory(context: { - node: AppNode; - config: TConfig; - inputs: Expand>; - }): Expand>; - // (undocumented) - inputs?: TInputs; - // (undocumented) - kind?: string; - // (undocumented) - name?: string; - // (undocumented) - namespace?: string; - // (undocumented) - output: TOutput; -} - -// @public @deprecated (undocumented) -export interface LegacyExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { - singleton: boolean; - optional: boolean; - }, -> { - // (undocumented) - $$type: '@backstage/ExtensionInput'; - // (undocumented) - config: TConfig; - // (undocumented) - extensionData: TExtensionDataMap; -} - export { microsoftAuthApiRef }; // @public @@ -1906,17 +1475,12 @@ export type ResolvedExtensionInput< ? { node: AppNode; } & ExtensionDataContainer - : TExtensionInput['extensionData'] extends AnyExtensionDataMap - ? { - node: AppNode; - output: ExtensionDataValues; - } : never; // @public export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput | LegacyExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index e5af309ba1..b22d76cd15 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -5,7 +5,6 @@ ```ts /// -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; @@ -13,31 +12,10 @@ import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; -import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -// @alpha @deprecated (undocumented) -export const catalogExtensionData: { - entityContentTitle: ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - >; - entityFilterFunction: ConfigurableExtensionDataRef< - (entity: Entity) => boolean, - 'catalog.entity-filter-function', - {} - >; - entityFilterExpression: ConfigurableExtensionDataRef< - string, - 'catalog.entity-filter-expression', - {} - >; -}; - // @alpha (undocumented) export const catalogReactTranslationRef: TranslationRef< 'catalog-react', @@ -120,82 +98,6 @@ export function convertLegacyEntityContentExtension( }, ): ExtensionDefinition; -// @alpha @deprecated (undocumented) -export function createEntityCardExtension< - TConfig extends { - filter?: string; - }, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; -}): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @alpha @deprecated (undocumented) -export function createEntityContentExtension< - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - defaultPath: string; - defaultTitle: string; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - inputs: Expand>; - }) => Promise; -}): ExtensionDefinition< - { - title: string; - path: string; - filter?: string | undefined; - }, - { - filter?: string | undefined; - title?: string | undefined; - path?: string | undefined; - }, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - // @alpha export const EntityCardBlueprint: ExtensionBlueprint< { diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 806fad5754..42f4355921 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -7,7 +7,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; @@ -18,7 +17,6 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; @@ -126,28 +124,6 @@ export const catalogTranslationRef: TranslationRef< } >; -// @alpha @deprecated (undocumented) -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - // @alpha (undocumented) const _default: BackstagePlugin< { diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index bce620c378..57bf03114f 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -7,9 +7,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; -import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ListItemProps } from '@material-ui/core/ListItem'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; @@ -19,38 +17,6 @@ export type BaseSearchResultListItemProps = T & { result?: SearchDocument; } & Omit; -// @alpha @deprecated -export function createSearchResultListItemExtension< - TConfig extends { - noTrack?: boolean; - }, ->( - options: SearchResultItemExtensionOptions, -): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - { - kind?: string | undefined; - namespace?: string | undefined; - name?: string | undefined; - } ->; - -// @alpha @deprecated (undocumented) -export namespace createSearchResultListItemExtension { - const // @deprecated (undocumented) - itemDataRef: ConfigurableExtensionDataRef< - { - predicate?: SearchResultItemExtensionPredicate | undefined; - component: SearchResultItemExtensionComponent; - }, - 'search.search-result-list-item.item', - {} - >; -} - // @alpha (undocumented) export type SearchResultItemExtensionComponent = < P extends BaseSearchResultListItemProps, @@ -58,25 +24,6 @@ export type SearchResultItemExtensionComponent = < props: P, ) => JSX.Element | null; -// @alpha @deprecated (undocumented) -export type SearchResultItemExtensionOptions< - TConfig extends { - noTrack?: boolean; - }, -> = { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - component: (options: { - config: TConfig; - }) => Promise; - predicate?: SearchResultItemExtensionPredicate; -}; - // @alpha (undocumented) export type SearchResultItemExtensionPredicate = ( result: SearchResult, From 54460616ff81885d9333c1a4de5f46fcf8a5e401 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Aug 2024 15:27:17 +0200 Subject: [PATCH 388/393] changesets: add changesets for v1 extension removal Signed-off-by: Patrik Oldsberg --- .changeset/brown-boxes-arrive.md | 5 +++++ .changeset/empty-coats-sparkle.md | 5 +++++ .changeset/serious-cheetahs-help.md | 7 +++++++ .changeset/sharp-mayflies-beg.md | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 .changeset/brown-boxes-arrive.md create mode 100644 .changeset/empty-coats-sparkle.md create mode 100644 .changeset/serious-cheetahs-help.md create mode 100644 .changeset/sharp-mayflies-beg.md diff --git a/.changeset/brown-boxes-arrive.md b/.changeset/brown-boxes-arrive.md new file mode 100644 index 0000000000..edefc0dcaf --- /dev/null +++ b/.changeset/brown-boxes-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed support for "v1" extensions. This means that it is no longer possible to declare inputs and outputs as objects when using `createExtension`. In addition, all extension creators except for `createComponentExtension` have been removed, use the equivalent blueprint instead. See the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations/#130) for more information on this change. diff --git a/.changeset/empty-coats-sparkle.md b/.changeset/empty-coats-sparkle.md new file mode 100644 index 0000000000..7e3b791c6b --- /dev/null +++ b/.changeset/empty-coats-sparkle.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +Removed support for testing "v1" extensions, where outputs are defined as an object rather than an array. diff --git a/.changeset/serious-cheetahs-help.md b/.changeset/serious-cheetahs-help.md new file mode 100644 index 0000000000..fa9debcf8c --- /dev/null +++ b/.changeset/serious-cheetahs-help.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-catalog': patch +--- + +The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. diff --git a/.changeset/sharp-mayflies-beg.md b/.changeset/sharp-mayflies-beg.md new file mode 100644 index 0000000000..c78335c187 --- /dev/null +++ b/.changeset/sharp-mayflies-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Internal refactor following removal of v1 extension support. The app implementation itself still supports v1 extensions at runtime. From 9bd5088b4a35011f70026a78e76823ef3a995726 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Aug 2024 12:14:12 +0200 Subject: [PATCH 389/393] changesets: link to 1.30 migration docs Signed-off-by: Patrik Oldsberg --- .changeset/serious-cheetahs-help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/serious-cheetahs-help.md b/.changeset/serious-cheetahs-help.md index fa9debcf8c..88bad696c5 100644 --- a/.changeset/serious-cheetahs-help.md +++ b/.changeset/serious-cheetahs-help.md @@ -4,4 +4,4 @@ '@backstage/plugin-catalog': patch --- -The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. +The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). From acf0e68263fd2367d9ab56787551fb5adfc09c6e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Aug 2024 12:15:03 +0200 Subject: [PATCH 390/393] frontend-plugin-api: update test snapshots for createExtensionOverrides Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtensionOverrides.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index ef95f82e25..2219f963ed 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -74,9 +74,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "a", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, { "$$type": "@backstage/Extension", @@ -89,9 +89,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "b", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, { "$$type": "@backstage/Extension", @@ -104,9 +104,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "k:c/n", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, ], "featureFlags": [], From 17e337e13f3075637dc6b8bb91f3b1dddeb01f74 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Aug 2024 13:16:05 +0200 Subject: [PATCH 391/393] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- docs/releases/v1.30.0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/v1.30.0.md b/docs/releases/v1.30.0.md index e441ce9687..230dc54813 100644 --- a/docs/releases/v1.30.0.md +++ b/docs/releases/v1.30.0.md @@ -16,7 +16,7 @@ This release marks another big milestone for the New Frontend System. **We encou At the end of last year in the [1.21 release](https://backstage.io/docs/releases/v1.21.0), we shipped the New Frontend System Alpha. It marked a more stable release of the new system, but we knew there was still much more work left to be done. Since then we have received valuable feedback and identified key areas of improvement. In particular around the creation of new extension kinds as well as overriding and testing of extensions. -Over the summer months we’ve been working hard towards addressing this feedback and getting the New Frontend System in shape for us to be confident in encouraging broader adoption by plugins. For a summary of the changes you can check out the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130), or can see the ongoing progress in the [meta issue](https://github.com/backstage/backstage/issues/19545). With this release comes some new features, deprecations and breaking changes. +Over the summer months we’ve been working hard towards addressing this feedback and getting the New Frontend System in shape for us to be confident in encouraging broader adoption by plugins. For a summary of the changes you can check out the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130), or can see the ongoing progress in the [meta issue](https://github.com/backstage/backstage/issues/19545). With this release comes some new features, deprecations and breaking changes in the `@backstage/frontend-app-api`, `@backstage/frontend-plugin-api`, and `@backstage/core-compat-api` packages.``` **Breaking**: @@ -90,7 +90,7 @@ Example: auth: providers: awsalb: - issuer: … + issuer: ... # put your actual ARN here + signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' region: ... From b246e1eb8c55612d79c3d08251d531e7c302189d Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Tue, 20 Aug 2024 15:17:03 +0200 Subject: [PATCH 392/393] Update mighty-days-kiss.md Signed-off-by: Ben Lambert --- .changeset/mighty-days-kiss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/mighty-days-kiss.md b/.changeset/mighty-days-kiss.md index 11b8c1b149..f78e602e25 100644 --- a/.changeset/mighty-days-kiss.md +++ b/.changeset/mighty-days-kiss.md @@ -3,4 +3,4 @@ '@backstage/backend-app-api': patch --- -change externalAccess to optional +`auth.externalAccess` should be optional in the config schema From 1284ac0915c66e6f8742dbaefecf1658cf19aadf Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 20 Aug 2024 09:27:19 -0500 Subject: [PATCH 393/393] add dependency Signed-off-by: Paul Schultz --- beps/00010-event-auditor/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/beps/00010-event-auditor/README.md b/beps/00010-event-auditor/README.md index b1af69cf95..8a0091c972 100644 --- a/beps/00010-event-auditor/README.md +++ b/beps/00010-event-auditor/README.md @@ -176,6 +176,7 @@ The release plan involves initially creating a shared audit event package. Follo ## Dependencies - `@backstage/types` +- ## Alternatives