diff --git a/.changeset/breezy-carpets-tie.md b/.changeset/breezy-carpets-tie.md
new file mode 100644
index 0000000000..7b106b071f
--- /dev/null
+++ b/.changeset/breezy-carpets-tie.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-search': patch
+---
+
+When the search modal is opened, the focus is placed on the search bar input field.
diff --git a/.changeset/empty-glasses-pump.md b/.changeset/empty-glasses-pump.md
new file mode 100644
index 0000000000..df5269052e
--- /dev/null
+++ b/.changeset/empty-glasses-pump.md
@@ -0,0 +1,5 @@
+---
+'@backstage/integration-aws-node': patch
+---
+
+Skip STS API calls where not needed, to support Minio use cases
diff --git a/.changeset/hungry-weeks-flash.md b/.changeset/hungry-weeks-flash.md
new file mode 100644
index 0000000000..19cb6cede8
--- /dev/null
+++ b/.changeset/hungry-weeks-flash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-tasks': patch
+---
+
+Added a new static `TaskScheduler.forPlugin` method.
diff --git a/.changeset/large-tools-collect.md b/.changeset/large-tools-collect.md
new file mode 100644
index 0000000000..1b3f31fbd2
--- /dev/null
+++ b/.changeset/large-tools-collect.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-user-settings': patch
+---
+
+Minor update to the `UserSettingsIdentityCard` to have clickable entity refs
diff --git a/.changeset/many-books-refuse.md b/.changeset/many-books-refuse.md
new file mode 100644
index 0000000000..8455254439
--- /dev/null
+++ b/.changeset/many-books-refuse.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Add `EntityLabelsCard` to show the labels for an entity.
diff --git a/.changeset/many-carpets-beam.md b/.changeset/many-carpets-beam.md
new file mode 100644
index 0000000000..454d404e83
--- /dev/null
+++ b/.changeset/many-carpets-beam.md
@@ -0,0 +1,14 @@
+---
+'@backstage/plugin-bazaar': patch
+'@backstage/plugin-fossa': patch
+'@backstage/plugin-github-actions': patch
+'@backstage/plugin-home': patch
+'@backstage/plugin-jenkins': patch
+'@backstage/plugin-kubernetes': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-stack-overflow': patch
+'@backstage/plugin-tech-radar': patch
+'@backstage/plugin-xcmetrics': patch
+---
+
+Small updates to some paragraph components to ensure theme typography properties are inherited correctly.
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 94fca81789..f1adc72242 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -1,5 +1,5 @@
{
- "mode": "pre",
+ "mode": "exit",
"tag": "next",
"initialVersions": {
"example-app": "0.2.78",
diff --git a/.changeset/proud-cobras-chew.md b/.changeset/proud-cobras-chew.md
new file mode 100644
index 0000000000..ae08378715
--- /dev/null
+++ b/.changeset/proud-cobras-chew.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-test-utils': patch
+---
+
+The backend started by `startTestBackend` now has default implementations of all core services. It now also returns a `TestBackend` instance, which provides access to the underlying `server` that can be used with testing libraries such as `supertest`.
diff --git a/.changeset/rich-bags-call.md b/.changeset/rich-bags-call.md
new file mode 100644
index 0000000000..ef9691bf74
--- /dev/null
+++ b/.changeset/rich-bags-call.md
@@ -0,0 +1,8 @@
+---
+'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/backend-app-api': patch
+'@backstage/plugin-app-backend': patch
+---
+
+Internal refactor to match new options pattern in the experimental backend system.
diff --git a/.changeset/rude-pumas-draw.md b/.changeset/rude-pumas-draw.md
new file mode 100644
index 0000000000..e36d1dd997
--- /dev/null
+++ b/.changeset/rude-pumas-draw.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-tech-insights-backend': patch
+---
+
+Expose optional `persistenceContext` on `TechInsights` construction to enable integrators to provide their own database implementations for fact handling.
diff --git a/.changeset/shaggy-apricots-camp.md b/.changeset/shaggy-apricots-camp.md
new file mode 100644
index 0000000000..705dcef0f0
--- /dev/null
+++ b/.changeset/shaggy-apricots-camp.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-app-api': patch
+---
+
+Exported the default root HTTP router implementation as `DefaultRootHttpRouter`. It only implements the routing layer and needs to be exposed via an HTTP server similar to the built-in setup in the `rootHttpRouterFactory`.
diff --git a/.changeset/slow-pumas-tap.md b/.changeset/slow-pumas-tap.md
new file mode 100644
index 0000000000..eabcb5fd94
--- /dev/null
+++ b/.changeset/slow-pumas-tap.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-plugin-api': minor
+---
+
+Updated all factory function creators to accept options as a top-level callback rather than extra parameter to the main factory function.
diff --git a/.eslintrc.js b/.eslintrc.js
index ea73a299c3..1616404a6f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -247,6 +247,7 @@ module.exports = {
{
forbid: [
{ element: 'button', message: 'use MUI instead' },
+ { element: 'p', message: 'use MUI instead' },
{
element: 'span',
message: 'use a MUI variant instead',
diff --git a/ADOPTERS.md b/ADOPTERS.md
index c8dd0fc03e..7ed897f351 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -226,4 +226,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Tractable AI](https://tractable.ai/) | [Stephan Schielke](https://github.com/stephanschielke) | We are hitting a critical point in our scale (100+ engineers) and need to get a handle on discoverability and ownership. The Service Catalog, TechDocs and Search are essential to us to achieve that. |
| [Garanti BBVA Teknoloji](https://www.linkedin.com/company/garanti-teknoloji/) | [Caglar Cataloglu](https://github.com/crozwise) | We are using Backstage focusing on improving experience of developers, minimizing friction from idea to production. We call our portal as "Hyperspace" and very excited for our community (2000+ engineers) that finally we have a platform to boost our productivity!
| [Booking.com](https://www.linkedin.com/company/booking.com/) | [Mesut Yilmazyildirim](https://www.linkedin.com/in/myilmazyildirim) | We are adopting Backstage as the new reliability platform inside the company. We are migrating UIs of our internal developer tools to Backstage for a better user experience.
-[XP Inc.](https://www.linkedin.com/company/xpinc/) | [Gabriel Santos](https://www.linkedin.com/in/gabriel-santos-6bb740a/) | Developer Portal Catalog (components, APIs, resources, users, groups) and organization relations.
+| [Swissquote Bank](https://swissquote.com/company/jobs/open-positions) | [Bruno Rocha](https://www.linkedin.com/in/bruno-rocha1/) | Integrating Backstage as the visualization layer & tactical overview of our services and teams.
+| [XP Inc.](https://www.linkedin.com/company/xpinc/) | [Gabriel Santos](https://www.linkedin.com/in/gabriel-santos-6bb740a/) | Developer Portal Catalog (components, APIs, resources, users, groups) and organization relations.
diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md
index 92ce153da2..31c0ed90db 100644
--- a/docs/features/software-catalog/extending-the-model.md
+++ b/docs/features/software-catalog/extending-the-model.md
@@ -438,7 +438,7 @@ with a new Entity type.
### Creating a custom entity definition
The first step of introducing a custom entity is to define what shape and schema
-it has. We do this both using a TypeScript type, along with a JSONSchema schema.
+it has. We do this using a TypeScript type, as well as a JSONSchema schema.
Most of the time you will want to have at least the TypeScript type of your
extension available in both frontend and backend code, which means you likely
@@ -474,23 +474,35 @@ entity might look like:
import { CatalogProcessor, processingResult } from '@backstage/catalog-backend';
import { entityKindSchemaValidator } from '@backstage/catalog-model';
+// For an example of the JSONSchema format and how to use $ref markers to the
+// base definitions, see:
+// https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
+import { foobarEntityV1alpha1Schema } from '@internal/catalog-model';
+
export class FoobarEntitiesProcessor implements CatalogProcessor {
// You often end up wanting to support multiple versions of your kind as you
- // iterate on the definition, so we keep each version inside this array.
+ // iterate on the definition, so we keep each version inside this array as a
+ // convenient pattern.
private readonly validators = [
- // This is where we use the JSONSchema that we export from our isomorphic package
+ // This is where we use the JSONSchema that we export from our isomorphic
+ // package
entityKindSchemaValidator(foobarEntityV1alpha1Schema),
];
- // validateEntityKind is responsible for signaling to the catalog processing engine
- // that this entity is valid and should therefore be submitted for further processing.
+ // validateEntityKind is responsible for signaling to the catalog processing
+ // engine that this entity is valid and should therefore be submitted for
+ // further processing.
async validateEntityKind(entity: Entity): Promise {
for (const validator of this.validators) {
+ // If the validator throws an exception, the entity will be marked as
+ // invalid.
if (validator(entity)) {
return true;
}
}
+ // Returning false signals that we don't know what this is, passing the
+ // responsibility to other processors to try to validate it instead.
return false;
}
@@ -505,8 +517,8 @@ export class FoobarEntitiesProcessor implements CatalogProcessor {
) {
const foobarEntity = entity as FoobarEntityV1alpha1;
- // Here we can modify the entity or emit results related to the entity
- // Typically you will want to emit any relations associated with the entity here
+ // Typically you will want to emit any relations associated with the
+ // entity here.
emit(processingResult.relation({ ... }))
}
@@ -519,8 +531,7 @@ Once the processor is created it can be wired up to the catalog via the
`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`:
```diff
-+ import { FoobarEntitiesProcessor implements CatalogProcessor {
- } from '@internal/plugin-foobar-backend';
++ import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
// ...
diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md
index 120a0817a9..57fafd1c28 100644
--- a/docs/overview/threat-model.md
+++ b/docs/overview/threat-model.md
@@ -54,7 +54,7 @@ Backstage also supports authentication through a proxy where the user identity i
## Catalog
-Integrators should configure `catalog.rules` and `catalog.locations[].rules` to limit the allowed entity kinds that users can define. In general it is best to restrict definition of User, Group, and Template entities so that internal users cannot register additional ones. Template entities define actions that are executed on the backend hosts, and while the goal is for these actions to be secure regardless of input, it is still a more sensitive context and it is recommended that you protect it with additional checks. It is very important to not allow registration of User and Group entities if you ingest and rely on these as organizational data in your catalog. Doing so could otherwise open up for the ability to impersonate users and confuse group membership information. You should always ingest organizational data using a statically configured catalog location or an entity provider reading from a trusted source. The entities emitted directly by an entity provider are always trusted and rules are not applied to them, but any entities produced further down the chain are still subject to the rules.
+Integrators should configure [catalog rules](https://backstage.io/docs/features/software-catalog/configuration#catalog-rules) to limit the allowed entity kinds that users can define. In general it is best to restrict definition of User, Group, and Template entities so that internal users cannot register additional ones. Template entities define actions that are executed on the backend hosts, and while the goal is for these actions to be secure regardless of input, it is still a more sensitive context and it is recommended that you protect it with additional checks. It is very important to not allow registration of User and Group entities if you ingest and rely on these as organizational data in your catalog. Doing so could otherwise open up for the ability to impersonate users and confuse group membership information. You should always ingest organizational data using a statically configured catalog location or an entity provider reading from a trusted source. The entities emitted directly by an entity provider are always trusted and rules are not applied to them, but any entities produced further down the chain are still subject to the rules.
The Catalog does not aim to protect against resource exhaustion attacks in its default setup. If you need to prevent your internal users from being able to register large amounts of entities, then it is recommended to disable entity registration and use a different approach for discovering entities. One way to mitigate any resource exhaustion attacks is to only allow the catalog to read from trusted SCM sources that have an audit trail. Catalog currently lacks limits for entity hierarchy depth and entity size, which we hope to address in the future.
diff --git a/microsite/data/plugins/gitlab.yaml b/microsite/data/plugins/gitlab.yaml
index 689e8004fd..ff815956b5 100644
--- a/microsite/data/plugins/gitlab.yaml
+++ b/microsite/data/plugins/gitlab.yaml
@@ -1,13 +1,14 @@
---
title: GitLab
-author: Loblaw
-authorUrl: https://github.com/loblaw-sre/backstage-plugin-gitlab
+author: ImmobiliareLabs
+authorUrl: https://github.com/immobiliare
category: CI/CD
description: View GitLab pipelines, merge requests, languages and contributors.
-documentation: https://github.com/loblaw-sre/backstage-plugin-gitlab
+documentation: https://github.com/immobiliare/backstage-plugin-gitlab
iconUrl: https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png
-npmPackageName: '@loblaw/backstage-plugin-gitlab'
+npmPackageName: '@immobiliarelabs/backstage-plugin-gitlab'
tags:
- ci
- cd
+ - gitlab
addedDate: '2021-08-17'
diff --git a/microsite/static/css/custom.css b/microsite/static/css/custom.css
index e6a230ee52..59f9571a70 100644
--- a/microsite/static/css/custom.css
+++ b/microsite/static/css/custom.css
@@ -1210,7 +1210,8 @@ code {
}
.nav-footer .copyright {
- width: 600px;
+ width: 100%;
+ padding: 0 8px;
color: #fff;
margin: 0 auto;
font-size: 10pt;
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index f223924294..c5410c4f80 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -60,12 +60,12 @@ import { HomepageCompositionRoot } from '@backstage/plugin-home';
import { LighthousePage } from '@backstage/plugin-lighthouse';
import { NewRelicPage } from '@backstage/plugin-newrelic';
import {
- ScaffolderFieldExtensions,
ScaffolderPage,
NextScaffolderPage,
scaffolderPlugin,
ScaffolderLayouts,
} from '@backstage/plugin-scaffolder';
+import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import {
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 02b00f038c..3566800f0b 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -52,6 +52,7 @@ import {
EntityHasSystemsCard,
EntityLayout,
EntityLinksCard,
+ EntityLabelsCard,
EntityOrphanWarning,
EntityProcessingErrorsPanel,
EntitySwitch,
@@ -59,6 +60,7 @@ import {
isComponentType,
isKind,
isOrphan,
+ hasLabels,
} from '@internal/plugin-catalog-customized';
import {
Direction,
@@ -364,6 +366,14 @@ const overviewContent = (
+
+
+
+
+
+
+
+
{cicdCard}
diff --git a/packages/app/src/components/search/SearchModal.tsx b/packages/app/src/components/search/SearchModal.tsx
index 4557857ba5..527351a857 100644
--- a/packages/app/src/components/search/SearchModal.tsx
+++ b/packages/app/src/components/search/SearchModal.tsx
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import React from 'react';
+import React, { KeyboardEvent, useRef, useCallback, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
DialogActions,
DialogContent,
@@ -74,29 +75,48 @@ const useStyles = makeStyles(theme => ({
viewResultsLink: { verticalAlign: '0.5em' },
}));
+const rootRouteRef = searchPlugin.routes.root;
+
export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => {
- const getSearchLink = useRouteRef(searchPlugin.routes.root);
const classes = useStyles();
+ const navigate = useNavigate();
+ const { transitions } = useTheme();
+ const { focusContent } = useContent();
const catalogApi = useApi(catalogApiRef);
- const { term, types } = useSearch();
- const { focusContent } = useContent();
- const { transitions } = useTheme();
- const handleResultClick = () => {
+ const { term, types } = useSearch();
+ const searchBarRef = useRef(null);
+ const searchPagePath = `${useRouteRef(rootRouteRef)()}?query=${term}`;
+
+ useEffect(() => {
+ searchBarRef?.current?.focus();
+ });
+
+ const handleSearchResulClick = useCallback(() => {
toggleModal();
setTimeout(focusContent, transitions.duration.leavingScreen);
- };
+ }, [toggleModal, focusContent, transitions]);
- const handleKeyPress = () => {
- handleResultClick();
- };
+ const handleSearchBarKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ navigate(searchPagePath);
+ toggleModal();
+ }
+ },
+ [navigate, toggleModal, searchPagePath],
+ );
return (
<>
-
+
@@ -169,16 +189,7 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => {
alignItems="center"
>
- {
- toggleModal();
- setTimeout(
- focusContent,
- transitions.duration.leavingScreen,
- );
- }}
- to={`${getSearchLink()}?query=${term}`}
- >
+
void }) => {
role="button"
tabIndex={0}
key={`${document.location}-btn`}
- onClick={handleResultClick}
- onKeyPress={handleKeyPress}
+ onClick={handleSearchResulClick}
+ onKeyDown={handleSearchResulClick}
>
{resultItem}
diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md
index a8f077b760..199b0863de 100644
--- a/packages/backend-app-api/api-report.md
+++ b/packages/backend-app-api/api-report.md
@@ -12,6 +12,7 @@ import { CorsOptions } from 'cors';
import { ErrorRequestHandler } from 'express';
import { Express as Express_2 } from 'express';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
+import { Handler } from 'express';
import { HelmetOptions } from 'helmet';
import * as http from 'http';
import { HttpRouterService } from '@backstage/backend-plugin-api';
@@ -45,14 +46,10 @@ export interface Backend {
}
// @public (undocumented)
-export const cacheFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const cacheFactory: () => ServiceFactory;
// @public (undocumented)
-export const configFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const configFactory: () => ServiceFactory;
// @public
export function createHttpServer(
@@ -75,14 +72,25 @@ export interface CreateSpecializedBackendOptions {
}
// @public (undocumented)
-export const databaseFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const databaseFactory: () => ServiceFactory;
+
+// @public
+export class DefaultRootHttpRouter implements RootHttpRouterService {
+ // (undocumented)
+ static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter;
+ // (undocumented)
+ handler(): Handler;
+ // (undocumented)
+ use(path: string, handler: Handler): void;
+}
+
+// @public
+export interface DefaultRootHttpRouterOptions {
+ indexPath?: string | false;
+}
// @public (undocumented)
-export const discoveryFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const discoveryFactory: () => ServiceFactory;
// @public
export interface ExtendedHttpServer extends http.Server {
@@ -100,9 +108,9 @@ export const httpRouterFactory: (
) => ServiceFactory;
// @public (undocumented)
-export type HttpRouterFactoryOptions = {
+export interface HttpRouterFactoryOptions {
getPath(pluginId: string): string;
-};
+}
// @public
export type HttpServerCertificateOptions =
@@ -128,9 +136,7 @@ export type HttpServerOptions = {
};
// @public (undocumented)
-export const identityFactory: (
- options?: IdentityFactoryOptions | undefined,
-) => ServiceFactory;
+export const identityFactory: () => ServiceFactory;
// @public
export type IdentityFactoryOptions = {
@@ -139,14 +145,10 @@ export type IdentityFactoryOptions = {
};
// @public
-export const lifecycleFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const lifecycleFactory: () => ServiceFactory;
// @public (undocumented)
-export const loggerFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const loggerFactory: () => ServiceFactory;
// @public
export class MiddlewareFactory {
@@ -174,9 +176,7 @@ export interface MiddlewareFactoryOptions {
}
// @public (undocumented)
-export const permissionsFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const permissionsFactory: () => ServiceFactory;
// @public
export function readCorsOptions(config?: Config): CorsOptions;
@@ -204,9 +204,7 @@ export interface RootHttpRouterConfigureOptions {
}
// @public (undocumented)
-export const rootHttpRouterFactory: (
- options?: RootHttpRouterFactoryOptions | undefined,
-) => ServiceFactory;
+export const rootHttpRouterFactory: () => ServiceFactory;
// @public (undocumented)
export type RootHttpRouterFactoryOptions = {
@@ -215,19 +213,13 @@ export type RootHttpRouterFactoryOptions = {
};
// @public
-export const rootLifecycleFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const rootLifecycleFactory: () => ServiceFactory;
// @public (undocumented)
-export const rootLoggerFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const rootLoggerFactory: () => ServiceFactory;
// @public (undocumented)
-export const schedulerFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const schedulerFactory: () => ServiceFactory;
// @public (undocumented)
export type ServiceOrExtensionPoint =
@@ -235,12 +227,8 @@ export type ServiceOrExtensionPoint =
| ServiceRef;
// @public (undocumented)
-export const tokenManagerFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const tokenManagerFactory: () => ServiceFactory;
// @public (undocumented)
-export const urlReaderFactory: (
- options?: undefined,
-) => ServiceFactory;
+export const urlReaderFactory: () => ServiceFactory;
```
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index a6ecb5f455..01eaeaecaf 100644
--- a/packages/backend-app-api/package.json
+++ b/packages/backend-app-api/package.json
@@ -48,6 +48,7 @@
"express-promise-router": "^4.1.0",
"fs-extra": "10.1.0",
"helmet": "^6.0.0",
+ "lodash": "^4.17.21",
"minimatch": "^5.0.0",
"morgan": "^1.10.0",
"node-forge": "^1.3.1",
diff --git a/packages/backend-app-api/src/http/readCorsOptions.test.ts b/packages/backend-app-api/src/http/readCorsOptions.test.ts
index af490a26f9..ebedab860c 100644
--- a/packages/backend-app-api/src/http/readCorsOptions.test.ts
+++ b/packages/backend-app-api/src/http/readCorsOptions.test.ts
@@ -84,4 +84,42 @@ describe('readCorsOptions', () => {
expect(cors).toEqual(expect.objectContaining({}));
expect(cors?.origin).toBeUndefined();
});
+
+ it("get's undefined properties removed", () => {
+ const config = new ConfigReader({
+ cors: {
+ methods: ['get'],
+ maxAge: 100,
+ },
+ });
+ const cors = readCorsOptions(config);
+ expect(cors?.hasOwnProperty('origin')).toBeFalsy();
+ expect(cors?.hasOwnProperty('methods')).toBeTruthy();
+ expect(cors?.hasOwnProperty('allowedHeaders')).toBeFalsy();
+ expect(cors?.hasOwnProperty('exposedHeaders')).toBeFalsy();
+ expect(cors?.hasOwnProperty('credentials')).toBeFalsy();
+ expect(cors?.hasOwnProperty('maxAge')).toBeTruthy();
+ expect(cors?.hasOwnProperty('preflightContinue')).toBeFalsy();
+ expect(cors?.hasOwnProperty('optionsSuccessStatus')).toBeFalsy();
+ });
+
+ it('does not have undefined optionsSuccessStatus', () => {
+ const config = new ConfigReader({
+ cors: {
+ optionsSuccessStatus: undefined,
+ },
+ });
+ const cors = readCorsOptions(config);
+ expect(cors?.hasOwnProperty('optionsSuccessStatus')).toBeFalsy();
+ });
+
+ it('does have defined optionsSuccessStatus', () => {
+ const config = new ConfigReader({
+ cors: {
+ optionsSuccessStatus: 200,
+ },
+ });
+ const cors = readCorsOptions(config);
+ expect(cors?.optionsSuccessStatus).toBe(200);
+ });
});
diff --git a/packages/backend-app-api/src/http/readCorsOptions.ts b/packages/backend-app-api/src/http/readCorsOptions.ts
index b58cae5ded..d8ab1f01d7 100644
--- a/packages/backend-app-api/src/http/readCorsOptions.ts
+++ b/packages/backend-app-api/src/http/readCorsOptions.ts
@@ -36,7 +36,7 @@ export function readCorsOptions(config?: Config): CorsOptions {
return { origin: false }; // Disable CORS
}
- return {
+ return removeUnknown({
origin: createCorsOriginMatcher(readStringArray(cc, 'origin')),
methods: readStringArray(cc, 'methods'),
allowedHeaders: readStringArray(cc, 'allowedHeaders'),
@@ -45,7 +45,13 @@ export function readCorsOptions(config?: Config): CorsOptions {
maxAge: cc.getOptionalNumber('maxAge'),
preflightContinue: cc.getOptionalBoolean('preflightContinue'),
optionsSuccessStatus: cc.getOptionalNumber('optionsSuccessStatus'),
- };
+ });
+}
+
+function removeUnknown(obj: T): T {
+ return Object.fromEntries(
+ Object.entries(obj).filter(([, v]) => v !== undefined),
+ ) as T;
}
function readStringArray(config: Config, key: string): string[] | undefined {
diff --git a/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts
index f33e825c9b..52cecdbc4d 100644
--- a/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts
@@ -19,6 +19,7 @@ import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
+import { ConfigReader } from '@backstage/config';
/** @public */
export const databaseFactory = createServiceFactory({
@@ -28,7 +29,16 @@ export const databaseFactory = createServiceFactory({
plugin: coreServices.pluginMetadata,
},
async factory({ config }) {
- const databaseManager = DatabaseManager.fromConfig(config);
+ const databaseManager = config.getOptional('backend.database')
+ ? DatabaseManager.fromConfig(config)
+ : DatabaseManager.fromConfig(
+ new ConfigReader({
+ backend: {
+ database: { client: 'better-sqlite3', connection: ':memory:' },
+ },
+ }),
+ );
+
return async ({ plugin }) => {
return databaseManager.forPlugin(plugin.getId());
};
diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterFactory.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterFactory.ts
index f1fc089ea3..8787b1ad9e 100644
--- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterFactory.ts
@@ -23,30 +23,32 @@ import { Handler } from 'express';
/**
* @public
*/
-export type HttpRouterFactoryOptions = {
+export interface HttpRouterFactoryOptions {
/**
* A callback used to generate the path for each plugin, defaults to `/api/{pluginId}`.
*/
getPath(pluginId: string): string;
-};
+}
/** @public */
-export const httpRouterFactory = createServiceFactory({
- service: coreServices.httpRouter,
- deps: {
- plugin: coreServices.pluginMetadata,
- rootHttpRouter: coreServices.rootHttpRouter,
- },
- async factory({ rootHttpRouter }, options?: HttpRouterFactoryOptions) {
- const getPath = options?.getPath ?? (id => `/api/${id}`);
+export const httpRouterFactory = createServiceFactory(
+ (options?: HttpRouterFactoryOptions) => ({
+ service: coreServices.httpRouter,
+ deps: {
+ plugin: coreServices.pluginMetadata,
+ rootHttpRouter: coreServices.rootHttpRouter,
+ },
+ async factory({ rootHttpRouter }) {
+ const getPath = options?.getPath ?? (id => `/api/${id}`);
- return async ({ plugin }) => {
- const path = getPath(plugin.getId());
- return {
- use(handler: Handler) {
- rootHttpRouter.use(path, handler);
- },
+ return async ({ plugin }) => {
+ const path = getPath(plugin.getId());
+ return {
+ use(handler: Handler) {
+ rootHttpRouter.use(path, handler);
+ },
+ };
};
- };
- },
-});
+ },
+ }),
+);
diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts
similarity index 76%
rename from packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts
rename to packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts
index b72e87f77b..b72b30e02e 100644
--- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts
+++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import { RestrictedIndexedRouter } from './RestrictedIndexedRouter';
+import { DefaultRootHttpRouter } from './DefaultRootHttpRouter';
-describe('RestrictedIndexedRouter', () => {
+describe('DefaultRootHttpRouter', () => {
it.each([
[['/b'], '/a'],
[['/a'], '/aa/b'],
@@ -25,7 +25,7 @@ describe('RestrictedIndexedRouter', () => {
[['/b/a'], '/a'],
[['/a'], '/aa'],
])(`with existing paths %s, adds %s without conflict`, (existing, added) => {
- const router = new RestrictedIndexedRouter(false);
+ const router = DefaultRootHttpRouter.create();
for (const path of existing) {
router.use(path, () => {});
}
@@ -39,7 +39,7 @@ describe('RestrictedIndexedRouter', () => {
])(
`find conflict when existing paths %s, adds %s`,
(existing, added, conflict) => {
- const router = new RestrictedIndexedRouter(false);
+ const router = DefaultRootHttpRouter.create();
for (const path of existing) {
router.use(path, () => {});
}
@@ -48,4 +48,10 @@ describe('RestrictedIndexedRouter', () => {
);
},
);
+
+ it('should not be possible to supply an empty indexPath', () => {
+ expect(() => DefaultRootHttpRouter.create({ indexPath: '' })).toThrow(
+ 'indexPath option may not be an empty string',
+ );
+ });
});
diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts
similarity index 60%
rename from packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts
rename to packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts
index 961277f34d..0a144af494 100644
--- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts
+++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts
@@ -16,23 +16,59 @@
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
import { Handler, Router } from 'express';
+import trimEnd from 'lodash/trimEnd';
function normalizePath(path: string): string {
- return path.replace(/\/*$/, '/');
+ return `${trimEnd(path, '/')}/`;
}
-export class RestrictedIndexedRouter implements RootHttpRouterService {
- #indexPath?: false | string;
+/**
+ * Options for the {@link DefaultRootHttpRouter} class.
+ *
+ * @public
+ */
+export interface DefaultRootHttpRouterOptions {
+ /**
+ * The path to forward all unmatched requests to. Defaults to '/api/app' if
+ * not given. Disables index path behavior if false is given.
+ */
+ indexPath?: string | false;
+}
+
+/**
+ * The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for
+ * {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}.
+ *
+ * @public
+ */
+export class DefaultRootHttpRouter implements RootHttpRouterService {
+ #indexPath?: string;
#router = Router();
#namedRoutes = Router();
#indexRouter = Router();
#existingPaths = new Array();
- constructor(indexPath?: false | string) {
+ static create(options?: DefaultRootHttpRouterOptions) {
+ let indexPath;
+ if (options?.indexPath === false) {
+ indexPath = undefined;
+ } else if (options?.indexPath === undefined) {
+ indexPath = '/api/app';
+ } else if (options?.indexPath === '') {
+ throw new Error('indexPath option may not be an empty string');
+ } else {
+ indexPath = options.indexPath;
+ }
+ return new DefaultRootHttpRouter(indexPath);
+ }
+
+ private constructor(indexPath?: string) {
this.#indexPath = indexPath;
this.#router.use(this.#namedRoutes);
- this.#router.use(this.#indexRouter);
+ if (this.#indexPath) {
+ this.#router.use(this.#indexRouter);
+ }
}
use(path: string, handler: 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
index 1dfd72273d..e24662df05 100644
--- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts
+++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-export { rootHttpRouterFactory } from './rootHttpRouterFactory';
-export type {
- RootHttpRouterFactoryOptions,
- RootHttpRouterConfigureOptions,
+export {
+ rootHttpRouterFactory,
+ type RootHttpRouterFactoryOptions,
+ type RootHttpRouterConfigureOptions,
} from './rootHttpRouterFactory';
+export {
+ DefaultRootHttpRouter,
+ type DefaultRootHttpRouterOptions,
+} from './DefaultRootHttpRouter';
diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts
index 45cb826b4d..58f8cc2db1 100644
--- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts
@@ -27,7 +27,7 @@ import {
MiddlewareFactory,
readHttpServerOptions,
} from '../../../http';
-import { RestrictedIndexedRouter } from './RestrictedIndexedRouter';
+import { DefaultRootHttpRouter } from './DefaultRootHttpRouter';
/**
* @public
@@ -46,7 +46,8 @@ export interface RootHttpRouterConfigureOptions {
*/
export type RootHttpRouterFactoryOptions = {
/**
- * The path to forward all unmatched requests to. Defaults to '/api/app'
+ * The path to forward all unmatched requests to. Defaults to '/api/app' if
+ * not given. Disables index path behavior if false is given.
*/
indexPath?: string | false;
@@ -82,11 +83,10 @@ export const rootHttpRouterFactory = createServiceFactory({
configure = defaultConfigure,
}: RootHttpRouterFactoryOptions = {},
) {
- const router = new RestrictedIndexedRouter(indexPath ?? '/api/app');
const logger = rootLogger.child({ service: 'rootHttpRouter' });
-
const app = express();
+ const router = DefaultRootHttpRouter.create({ indexPath });
const middleware = MiddlewareFactory.create({ config, logger });
configure({
diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts
index a6edec868a..4e472b78f4 100644
--- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { loggerToWinstonLogger } from '@backstage/backend-common';
import {
coreServices,
createServiceFactory,
@@ -24,13 +25,17 @@ import { TaskScheduler } from '@backstage/backend-tasks';
export const schedulerFactory = createServiceFactory({
service: coreServices.scheduler,
deps: {
- config: coreServices.config,
plugin: coreServices.pluginMetadata,
+ databaseManager: coreServices.database,
+ logger: coreServices.logger,
},
- async factory({ config }) {
- const taskScheduler = TaskScheduler.fromConfig(config);
- return async ({ plugin }) => {
- return taskScheduler.forPlugin(plugin.getId());
+ async factory() {
+ return async ({ plugin, databaseManager, logger }) => {
+ return TaskScheduler.forPlugin({
+ pluginId: plugin.getId(),
+ databaseManager,
+ logger: loggerToWinstonLogger(logger),
+ });
};
},
});
diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts
index 9348f59d64..405a2fc630 100644
--- a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts
+++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts
@@ -143,7 +143,7 @@ describe('ServiceRegistry', () => {
const factory = createServiceFactory({
service: ref1,
deps: { rootDep: ref2 },
- async factory({ rootDep }) {
+ factory: async ({ rootDep }) => {
return async () => ({ x: rootDep.x });
},
});
diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md
index 213815f782..33190277c9 100644
--- a/packages/backend-plugin-api/api-report.md
+++ b/packages/backend-plugin-api/api-report.md
@@ -23,7 +23,7 @@ export interface BackendFeature {
}
// @public (undocumented)
-export interface BackendModuleConfig {
+export interface BackendModuleConfig {
// (undocumented)
moduleId: string;
// (undocumented)
@@ -31,16 +31,15 @@ export interface BackendModuleConfig {
// (undocumented)
register(
reg: Omit,
- options: TOptions,
): void;
}
// @public (undocumented)
-export interface BackendPluginConfig {
+export interface BackendPluginConfig {
// (undocumented)
id: string;
// (undocumented)
- register(reg: BackendRegistrationPoints, options: TOptions): void;
+ register(reg: BackendRegistrationPoints): void;
}
// @public (undocumented)
@@ -113,14 +112,14 @@ export namespace coreServices {
}
// @public
-export function createBackendModule(
- config: BackendModuleConfig,
-): FactoryFunctionWithOptions;
+export function createBackendModule(
+ config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
+): (...params: TOptions) => BackendFeature;
// @public (undocumented)
-export function createBackendPlugin(
- config: BackendPluginConfig,
-): FactoryFunctionWithOptions;
+export function createBackendPlugin(
+ config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
+): (...params: TOptions) => BackendFeature;
// @public (undocumented)
export function createExtensionPoint(
@@ -135,10 +134,14 @@ export function createServiceFactory<
TDeps extends {
[name in string]: ServiceRef;
},
- TOpts extends MaybeOptions = undefined,
+ TOpts extends [options?: object] = [],
>(
- config: ServiceFactoryConfig,
-): FactoryFunctionWithOptions, TOpts>;
+ config:
+ | ServiceFactoryConfig
+ | ((
+ ...options: TOpts
+ ) => ServiceFactoryConfig),
+): (...params: TOpts) => ServiceFactory;
// @public
export function createServiceRef(
@@ -337,14 +340,12 @@ export interface ServiceFactoryConfig<
TDeps extends {
[name in string]: ServiceRef;
},
- TOpts extends MaybeOptions = undefined,
> {
// (undocumented)
deps: TDeps;
// (undocumented)
factory(
deps: ServiceRefsToInstances,
- options: TOpts,
): TScope extends 'root'
? Promise
: Promise<(deps: ServiceRefsToInstances) => Promise>;
diff --git a/packages/backend-plugin-api/src/services/system/types.test.ts b/packages/backend-plugin-api/src/services/system/types.test.ts
index 0e02f38d3f..9cbc45151d 100644
--- a/packages/backend-plugin-api/src/services/system/types.test.ts
+++ b/packages/backend-plugin-api/src/services/system/types.test.ts
@@ -16,9 +16,12 @@
import { createServiceFactory, createServiceRef } from './types';
+const ref = createServiceRef({ id: 'x' });
+const rootDep = createServiceRef({ id: 'y', scope: 'root' });
+const pluginDep = createServiceRef({ id: 'z' });
+
describe('createServiceFactory', () => {
it('should create a meta factory with no options', () => {
- const ref = createServiceRef({ id: 'x' });
const metaFactory = createServiceFactory({
service: ref,
deps: {},
@@ -37,19 +40,19 @@ describe('createServiceFactory', () => {
metaFactory({ x: 1 });
// @ts-expect-error
metaFactory(null);
+ // @ts-expect-error
metaFactory(undefined);
metaFactory();
});
it('should create a meta factory with optional options', () => {
- const ref = createServiceRef({ id: 'x' });
- const metaFactory = createServiceFactory({
+ const metaFactory = createServiceFactory((_opts?: { x: number }) => ({
service: ref,
deps: {},
- async factory(_deps, _opts?: { x: number }) {
+ async factory() {
return async () => 'x';
},
- });
+ }));
expect(metaFactory).toEqual(expect.any(Function));
// @ts-expect-error
@@ -66,14 +69,13 @@ describe('createServiceFactory', () => {
});
it('should create a meta factory with required options', () => {
- const ref = createServiceRef({ id: 'x' });
- const metaFactory = createServiceFactory({
+ const metaFactory = createServiceFactory((_opts: { x: number }) => ({
service: ref,
deps: {},
- async factory(_deps, _opts: { x: number }) {
+ async factory() {
return async () => 'x';
},
- });
+ }));
expect(metaFactory).toEqual(expect.any(Function));
// @ts-expect-error
@@ -95,14 +97,13 @@ describe('createServiceFactory', () => {
interface TestOptions {
x: number;
}
- const ref = createServiceRef({ id: 'x' });
- const metaFactory = createServiceFactory({
+ const metaFactory = createServiceFactory((_opts?: TestOptions) => ({
service: ref,
deps: {},
- async factory(_deps, _opts?: TestOptions) {
+ async factory() {
return async () => 'x';
},
- });
+ }));
expect(metaFactory).toEqual(expect.any(Function));
// @ts-expect-error
@@ -122,14 +123,13 @@ describe('createServiceFactory', () => {
interface TestOptions {
x: number;
}
- const ref = createServiceRef({ id: 'x' });
- const metaFactory = createServiceFactory({
+ const metaFactory = createServiceFactory((_opts: TestOptions) => ({
service: ref,
deps: {},
- async factory(_deps, _opts: TestOptions) {
+ async factory() {
return async () => 'x';
},
- });
+ }));
expect(metaFactory).toEqual(expect.any(Function));
// @ts-expect-error
@@ -147,80 +147,163 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should only allow objects as options', () => {
- const ref = createServiceRef({ id: 'x' });
- const metaFactory = createServiceFactory({
+ it('should create factory with required options and dependencies', () => {
+ interface TestOptions {
+ x: number;
+ }
+
+ function unused(..._any: any[]) {}
+
+ const metaFactory = createServiceFactory((_opts: TestOptions) => ({
service: ref,
- deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: string) {
- return async () => 'x';
+ deps: {
+ root: rootDep,
+ plugin: pluginDep,
},
- });
+ async factory({ root }) {
+ const root1: number = root;
+ // @ts-expect-error
+ const root2: string = root;
+ return async ({ plugin }) => {
+ const plugin3: boolean = plugin;
+ // @ts-expect-error
+ const plugin4: number = plugin;
+ unused(root1, root2, plugin3, plugin4);
+ return 'x';
+ };
+ },
+ }));
expect(metaFactory).toEqual(expect.any(Function));
- createServiceFactory({
+
+ // @ts-expect-error
+ metaFactory('string');
+ // @ts-expect-error
+ metaFactory({});
+ metaFactory({ x: 1 });
+ // @ts-expect-error
+ metaFactory({ x: 1, y: 2 });
+ // @ts-expect-error
+ metaFactory(null);
+ // @ts-expect-error
+ metaFactory(undefined);
+ // @ts-expect-error
+ metaFactory();
+ });
+
+ it('should create factory with optional options and dependencies', () => {
+ interface TestOptions {
+ x: number;
+ }
+
+ function unused(..._any: any[]) {}
+
+ const metaFactory = createServiceFactory((_opts?: TestOptions) => ({
+ service: ref,
+ deps: {
+ root: rootDep,
+ plugin: pluginDep,
+ },
+ async factory({ root }) {
+ const root1: number = root;
+ // @ts-expect-error
+ const root2: string = root;
+ return async ({ plugin }) => {
+ const plugin3: boolean = plugin;
+ // @ts-expect-error
+ const plugin4: number = plugin;
+ unused(root1, root2, plugin3, plugin4);
+ return 'x';
+ };
+ },
+ }));
+ expect(metaFactory).toEqual(expect.any(Function));
+
+ // @ts-expect-error
+ metaFactory('string');
+ // @ts-expect-error
+ metaFactory({});
+ metaFactory({ x: 1 });
+ // @ts-expect-error
+ metaFactory({ x: 1, y: 2 });
+ // @ts-expect-error
+ metaFactory(null);
+ metaFactory(undefined);
+ metaFactory();
+ });
+
+ it('should only allow objects as options', () => {
+ // @ts-expect-error
+ const metaFactory = createServiceFactory((_opts: string) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: number) {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ expect(metaFactory).toEqual(expect.any(Function));
+ // @ts-expect-error
+ createServiceFactory((_opts: number) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: symbol) {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: symbol) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: bigint) {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: bigint) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: 'string') {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: 'string') => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: Array) {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: Array) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: Map) {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: Map) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: Set) {
+ async factory() {
return async () => 'x';
},
- });
- createServiceFactory({
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: Set) => ({
service: ref,
deps: {},
- // @ts-expect-error
- async factory(_deps, _opts: null) {
+ async factory() {
return async () => 'x';
},
- });
+ }));
+ // @ts-expect-error
+ createServiceFactory((_opts: null) => ({
+ service: ref,
+ deps: {},
+ async factory() {
+ return async () => 'x';
+ },
+ }));
});
});
diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts
index ffbbdb5f0c..34c0a50005 100644
--- a/packages/backend-plugin-api/src/services/system/types.ts
+++ b/packages/backend-plugin-api/src/services/system/types.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { FactoryFunctionWithOptions, MaybeOptions } from '../../types';
-
/**
* TODO
*
@@ -133,9 +131,7 @@ type ServiceRefsToInstances<
T extends { [key in string]: ServiceRef },
TScope extends 'root' | 'plugin' = 'root' | 'plugin',
> = {
- [name in {
- [key in keyof T]: T[key] extends ServiceRef ? key : never;
- }[keyof T]]: T[name] extends ServiceRef ? TImpl : never;
+ [key in keyof T as T[key]['scope'] extends TScope ? key : never]: T[key]['T'];
};
/** @public */
@@ -144,13 +140,11 @@ export interface ServiceFactoryConfig<
TScope extends 'root' | 'plugin',
TImpl extends TService,
TDeps extends { [name in string]: ServiceRef },
- TOpts extends MaybeOptions = undefined,
> {
service: ServiceRef;
deps: TDeps;
factory(
deps: ServiceRefsToInstances,
- options: TOpts,
): TScope extends 'root'
? Promise
: Promise<(deps: ServiceRefsToInstances) => Promise>;
@@ -164,17 +158,23 @@ export function createServiceFactory<
TScope extends 'root' | 'plugin',
TImpl extends TService,
TDeps extends { [name in string]: ServiceRef },
- TOpts extends MaybeOptions = undefined,
+ TOpts extends [options?: object] = [],
>(
- config: ServiceFactoryConfig,
-): FactoryFunctionWithOptions, TOpts> {
- return (options?: TOpts) =>
+ config:
+ | ServiceFactoryConfig
+ | ((
+ ...options: TOpts
+ ) => ServiceFactoryConfig),
+): (...params: TOpts) => ServiceFactory {
+ if (typeof config === 'function') {
+ return (...opts: TOpts) => {
+ const c = config(...opts);
+ return { ...c, scope: c.service.scope } as ServiceFactory;
+ };
+ }
+ return () =>
({
+ ...config,
scope: config.service.scope,
- service: config.service,
- deps: config.deps,
- factory(deps: ServiceRefsToInstances) {
- return config.factory(deps, options!);
- },
} as ServiceFactory);
}
diff --git a/packages/backend-plugin-api/src/wiring/factories.test.ts b/packages/backend-plugin-api/src/wiring/factories.test.ts
index cf67e411d5..dbdf0ead14 100644
--- a/packages/backend-plugin-api/src/wiring/factories.test.ts
+++ b/packages/backend-plugin-api/src/wiring/factories.test.ts
@@ -32,10 +32,10 @@ describe('createExtensionPoint', () => {
describe('createBackendPlugin', () => {
it('should create a BackendPlugin', () => {
- const plugin = createBackendPlugin({
+ const plugin = createBackendPlugin((_options: { a: string }) => ({
id: 'x',
- register(_reg, _options: { a: string }) {},
- });
+ register() {},
+ }));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin({ a: 'a' }).id).toBe('x');
@@ -46,10 +46,10 @@ describe('createBackendPlugin', () => {
});
it('should create plugins with optional options', () => {
- const plugin = createBackendPlugin({
+ const plugin = createBackendPlugin((_options?: { a: string }) => ({
id: 'x',
- register(_reg, _options?: { a: string }) {},
- });
+ register() {},
+ }));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin()).toBeDefined();
@@ -73,10 +73,10 @@ describe('createBackendPlugin', () => {
interface TestOptions {
a: string;
}
- const plugin = createBackendPlugin({
+ const plugin = createBackendPlugin((_options: TestOptions) => ({
id: 'x',
- register(_reg, _options: TestOptions) {},
- });
+ register() {},
+ }));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin({ a: 'a' }).id).toBe('x');
@@ -90,10 +90,10 @@ describe('createBackendPlugin', () => {
interface TestOptions {
a: string;
}
- const plugin = createBackendPlugin({
+ const plugin = createBackendPlugin((_options?: TestOptions) => ({
id: 'x',
- register(_reg, _options?: TestOptions) {},
- });
+ register() {},
+ }));
expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
expect(plugin()).toBeDefined();
@@ -104,11 +104,11 @@ describe('createBackendPlugin', () => {
describe('createBackendModule', () => {
it('should create a BackendModule', () => {
- const mod = createBackendModule({
+ const mod = createBackendModule((_options: { a: string }) => ({
pluginId: 'x',
moduleId: 'y',
- register(_reg, _options: { a: string }) {},
- });
+ register() {},
+ }));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod({ a: 'a' }).id).toBe('x.y');
@@ -119,11 +119,11 @@ describe('createBackendModule', () => {
});
it('should create modules with optional options', () => {
- const mod = createBackendModule({
+ const mod = createBackendModule((_options?: { a: string }) => ({
pluginId: 'x',
moduleId: 'y',
- register(_reg, _options?: { a: string }) {},
- });
+ register() {},
+ }));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod()).toBeDefined();
@@ -148,11 +148,11 @@ describe('createBackendModule', () => {
interface TestOptions {
a: string;
}
- const mod = createBackendModule({
+ const mod = createBackendModule((_options: TestOptions) => ({
pluginId: 'x',
moduleId: 'y',
- register(_reg, _options: TestOptions) {},
- });
+ register() {},
+ }));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod({ a: 'a' }).id).toBe('x.y');
@@ -166,11 +166,11 @@ describe('createBackendModule', () => {
interface TestOptions {
a: string;
}
- const mod = createBackendModule({
+ const mod = createBackendModule((_options?: TestOptions) => ({
pluginId: 'x',
moduleId: 'y',
- register(_reg, _options?: TestOptions) {},
- });
+ register() {},
+ }));
expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
expect(mod()).toBeDefined();
diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts
index 4c384840cd..31a64778f2 100644
--- a/packages/backend-plugin-api/src/wiring/factories.ts
+++ b/packages/backend-plugin-api/src/wiring/factories.ts
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { FactoryFunctionWithOptions, MaybeOptions } from '../types';
import {
BackendRegistrationPoints,
BackendFeature,
@@ -43,30 +42,28 @@ export function createExtensionPoint(
}
/** @public */
-export interface BackendPluginConfig {
+export interface BackendPluginConfig {
id: string;
- register(reg: BackendRegistrationPoints, options: TOptions): void;
+ register(reg: BackendRegistrationPoints): void;
}
/** @public */
-export function createBackendPlugin(
- config: BackendPluginConfig,
-): FactoryFunctionWithOptions {
- return (options?: TOptions) => ({
- id: config.id,
- register(register: BackendRegistrationPoints) {
- return config.register(register, options!);
- },
- });
+export function createBackendPlugin(
+ config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
+): (...params: TOptions) => BackendFeature {
+ if (typeof config === 'function') {
+ return config;
+ }
+
+ return () => config;
}
/** @public */
-export interface BackendModuleConfig {
+export interface BackendModuleConfig {
pluginId: string;
moduleId: string;
register(
reg: Omit,
- options: TOptions,
): void;
}
@@ -84,14 +81,23 @@ export interface BackendModuleConfig {
*
* The `pluginId` should exactly match the `id` of the plugin that the module extends.
*/
-export function createBackendModule(
- config: BackendModuleConfig,
-): FactoryFunctionWithOptions {
- return (options?: TOptions) => ({
+export function createBackendModule(
+ config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
+): (...params: TOptions) => BackendFeature {
+ if (typeof config === 'function') {
+ return (...options: TOptions) => {
+ const c = config(...options);
+ return {
+ id: `${c.pluginId}.${c.moduleId}`,
+ register: c.register,
+ };
+ };
+ }
+ return () => ({
id: `${config.pluginId}.${config.moduleId}`,
register(register: BackendRegistrationPoints) {
// TODO: Hide registerExtensionPoint
- return config.register(register, options!);
+ return config.register(register);
},
});
}
diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md
index f7b4782263..44ec4addbe 100644
--- a/packages/backend-tasks/api-report.md
+++ b/packages/backend-tasks/api-report.md
@@ -8,6 +8,7 @@ import { DatabaseManager } from '@backstage/backend-common';
import { Duration } from 'luxon';
import { HumanDuration as HumanDuration_2 } from '@backstage/types';
import { Logger } from 'winston';
+import { PluginDatabaseManager } from '@backstage/backend-common';
// @public @deprecated
export type HumanDuration = HumanDuration_2;
@@ -74,6 +75,12 @@ export class TaskScheduler {
constructor(databaseManager: DatabaseManager, logger: Logger);
forPlugin(pluginId: string): PluginTaskScheduler;
// (undocumented)
+ static forPlugin(opts: {
+ pluginId: string;
+ databaseManager: PluginDatabaseManager;
+ logger: Logger;
+ }): PluginTaskScheduler;
+ // (undocumented)
static fromConfig(
config: Config,
options?: {
diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts
index f14fb9fc2b..fe81a0054e 100644
--- a/packages/backend-tasks/src/tasks/TaskScheduler.ts
+++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-import { DatabaseManager, getRootLogger } from '@backstage/backend-common';
+import {
+ DatabaseManager,
+ getRootLogger,
+ PluginDatabaseManager,
+} from '@backstage/backend-common';
import { Config } from '@backstage/config';
import { once } from 'lodash';
import { Duration } from 'luxon';
@@ -57,27 +61,35 @@ export class TaskScheduler {
* @returns A {@link PluginTaskScheduler} instance
*/
forPlugin(pluginId: string): PluginTaskScheduler {
- const databaseFactory = once(async () => {
- const databaseManager = this.databaseManager.forPlugin(pluginId);
- const knex = await databaseManager.getClient();
+ return TaskScheduler.forPlugin({
+ pluginId,
+ databaseManager: this.databaseManager.forPlugin(pluginId),
+ logger: this.logger,
+ });
+ }
- if (!databaseManager.migrations?.skip) {
+ static forPlugin(opts: {
+ pluginId: string;
+ databaseManager: PluginDatabaseManager;
+ logger: Logger;
+ }): PluginTaskScheduler {
+ const databaseFactory = once(async () => {
+ const knex = await opts.databaseManager.getClient();
+
+ if (!opts.databaseManager.migrations?.skip) {
await migrateBackendTasks(knex);
}
const janitor = new PluginTaskSchedulerJanitor({
knex,
waitBetweenRuns: Duration.fromObject({ minutes: 1 }),
- logger: this.logger,
+ logger: opts.logger,
});
janitor.start();
return knex;
});
- return new PluginTaskSchedulerImpl(
- databaseFactory,
- this.logger.child({ plugin: pluginId }),
- );
+ return new PluginTaskSchedulerImpl(databaseFactory, opts.logger);
}
}
diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md
index 3df3f3cb21..85cdb201ef 100644
--- a/packages/backend-test-utils/api-report.md
+++ b/packages/backend-test-utils/api-report.md
@@ -5,7 +5,10 @@
```ts
import { Backend } from '@backstage/backend-app-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
+import { ConfigService } from '@backstage/backend-plugin-api';
+import { ExtendedHttpServer } from '@backstage/backend-app-api';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
+import { JsonObject } from '@backstage/types';
import { Knex } from 'knex';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
@@ -13,6 +16,15 @@ import { ServiceRef } from '@backstage/backend-plugin-api';
// @public (undocumented)
export function isDockerDisabledForTests(): boolean;
+// @public (undocumented)
+export const mockConfigFactory: (
+ options?:
+ | {
+ data?: JsonObject | undefined;
+ }
+ | undefined,
+) => ServiceFactory;
+
// @public
export function setupRequestMockHandlers(worker: {
listen: (t: any) => void;
@@ -24,7 +36,14 @@ export function setupRequestMockHandlers(worker: {
export function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
->(options: TestBackendOptions): Promise;
+>(
+ options: TestBackendOptions,
+): Promise;
+
+// @alpha (undocumented)
+export interface TestBackend extends Backend {
+ readonly server: ExtendedHttpServer;
+}
// @alpha (undocumented)
export interface TestBackendOptions<
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index 38db9d6413..725be0d905 100644
--- a/packages/backend-test-utils/package.json
+++ b/packages/backend-test-utils/package.json
@@ -39,7 +39,10 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
+ "@backstage/types": "workspace:^",
"better-sqlite3": "^8.0.0",
+ "express": "^4.17.1",
+ "express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"msw": "^0.49.0",
"mysql2": "^2.2.5",
@@ -48,7 +51,9 @@
"uuid": "^8.0.0"
},
"devDependencies": {
- "@backstage/cli": "workspace:^"
+ "@backstage/cli": "workspace:^",
+ "@types/supertest": "^2.0.8",
+ "supertest": "^6.1.3"
},
"files": [
"dist",
diff --git a/packages/backend-test-utils/src/next/implementations/index.ts b/packages/backend-test-utils/src/next/implementations/index.ts
new file mode 100644
index 0000000000..55f417f8df
--- /dev/null
+++ b/packages/backend-test-utils/src/next/implementations/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { mockConfigFactory } from './mockConfigService';
diff --git a/packages/backend-test-utils/src/next/implementations/mockConfigService.ts b/packages/backend-test-utils/src/next/implementations/mockConfigService.ts
new file mode 100644
index 0000000000..a5578b866f
--- /dev/null
+++ b/packages/backend-test-utils/src/next/implementations/mockConfigService.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { ConfigReader } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
+
+/** @public */
+export const mockConfigFactory = createServiceFactory(
+ (options?: { data?: JsonObject }) => ({
+ service: coreServices.config,
+ deps: {},
+ async factory() {
+ return new ConfigReader(options?.data, 'mock-config');
+ },
+ }),
+);
diff --git a/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts b/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts
new file mode 100644
index 0000000000..dd1d6b16f7
--- /dev/null
+++ b/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { TokenManager } from '@backstage/backend-common';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
+
+class TokenManagerMock implements TokenManager {
+ async getToken(): Promise<{ token: string }> {
+ return { token: 'mock-token' };
+ }
+ async authenticate(token: string): Promise {
+ if (token !== 'mock-token') {
+ throw new Error('Invalid token');
+ }
+ }
+}
+
+export const mockTokenManagerFactory = createServiceFactory({
+ service: coreServices.tokenManager,
+ deps: {},
+ async factory() {
+ return async () => {
+ return new TokenManagerMock();
+ };
+ },
+});
diff --git a/packages/backend-test-utils/src/next/index.ts b/packages/backend-test-utils/src/next/index.ts
index 9bb5431772..9f9edfd837 100644
--- a/packages/backend-test-utils/src/next/index.ts
+++ b/packages/backend-test-utils/src/next/index.ts
@@ -15,3 +15,4 @@
*/
export * from './wiring';
+export * from './implementations';
diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
index 5ea37e9106..6358051c34 100644
--- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
+++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
@@ -20,7 +20,11 @@ import {
createServiceFactory,
createServiceRef,
coreServices,
+ createBackendPlugin,
} from '@backstage/backend-plugin-api';
+import { Router } from 'express';
+import request from 'supertest';
+
import { startTestBackend } from './TestBackend';
// This bit makes sure that test backends are cleaned up properly
@@ -156,4 +160,66 @@ describe('TestBackend', () => {
await backend.stop();
expect(shutdownSpy).toHaveBeenCalled();
});
+
+ it('should provide a set of default services', async () => {
+ expect.assertions(2);
+
+ const testPlugin = createBackendPlugin({
+ id: 'test',
+ register(env) {
+ env.registerInit({
+ deps: {
+ cache: coreServices.cache,
+ config: coreServices.config,
+ database: coreServices.database,
+ discovery: coreServices.discovery,
+ httpRouter: coreServices.httpRouter,
+ lifecycle: coreServices.lifecycle,
+ logger: coreServices.logger,
+ permissions: coreServices.permissions,
+ pluginMetadata: coreServices.pluginMetadata,
+ rootHttpRouter: coreServices.rootHttpRouter,
+ rootLifecycle: coreServices.rootLifecycle,
+ rootLogger: coreServices.rootLogger,
+ scheduler: coreServices.scheduler,
+ tokenManager: coreServices.tokenManager,
+ urlReader: coreServices.urlReader,
+ },
+ async init(deps) {
+ expect(Object.keys(deps)).toHaveLength(15);
+ expect(Object.values(deps)).not.toContain(undefined);
+ },
+ });
+ },
+ });
+
+ await startTestBackend({
+ services: [],
+ features: [testPlugin()],
+ });
+ });
+
+ it('should allow making requests via supertest', async () => {
+ const testPlugin = createBackendPlugin({
+ id: 'test',
+ register(env) {
+ env.registerInit({
+ deps: {
+ httpRouter: coreServices.httpRouter,
+ },
+ async init({ httpRouter }) {
+ const router = Router();
+ router.use('/ping-me', (_, res) => res.json({ message: 'pong' }));
+ httpRouter.use(router);
+ },
+ });
+ },
+ });
+
+ const { server } = await startTestBackend({ features: [testPlugin()] });
+
+ const res = await request(server).get('/api/test/ping-me');
+ expect(res.status).toEqual(200);
+ expect(res.body).toEqual({ message: 'pong' });
+ });
});
diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts
index 0295332855..8fe489750a 100644
--- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts
+++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts
@@ -21,15 +21,32 @@ import {
rootLifecycleFactory,
loggerFactory,
rootLoggerFactory,
+ cacheFactory,
+ permissionsFactory,
+ schedulerFactory,
+ urlReaderFactory,
+ databaseFactory,
+ httpRouterFactory,
+ MiddlewareFactory,
+ createHttpServer,
+ ExtendedHttpServer,
+ DefaultRootHttpRouter,
} from '@backstage/backend-app-api';
+import { SingleHostDiscovery } from '@backstage/backend-common';
import {
ServiceFactory,
ServiceRef,
createServiceFactory,
BackendFeature,
ExtensionPoint,
+ coreServices,
} from '@backstage/backend-plugin-api';
+import { mockConfigFactory } from '../implementations/mockConfigService';
+import { mockTokenManagerFactory } from '../implementations/mockTokenManagerService';
+import { ConfigReader } from '@backstage/config';
+import express from 'express';
+
/** @alpha */
export interface TestBackendOptions<
TServices extends any[],
@@ -54,11 +71,30 @@ export interface TestBackendOptions<
features?: BackendFeature[];
}
+/** @alpha */
+export interface TestBackend extends Backend {
+ /**
+ * Provides access to the underling HTTP server for use with utilities
+ * such as `supertest`.
+ *
+ * If the root http router service has been replaced, this will throw an error.
+ */
+ readonly server: ExtendedHttpServer;
+}
+
const defaultServiceFactories = [
- rootLoggerFactory(),
- loggerFactory(),
+ cacheFactory(),
+ databaseFactory(),
+ httpRouterFactory(),
lifecycleFactory(),
+ loggerFactory(),
+ mockConfigFactory(),
+ mockTokenManagerFactory(),
+ permissionsFactory(),
rootLifecycleFactory(),
+ rootLoggerFactory(),
+ schedulerFactory(),
+ urlReaderFactory(),
];
const backendInstancesToCleanUp = new Array();
@@ -67,7 +103,9 @@ const backendInstancesToCleanUp = new Array();
export async function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
->(options: TestBackendOptions): Promise {
+>(
+ options: TestBackendOptions,
+): Promise {
const {
services = [],
extensionPoints = [],
@@ -75,6 +113,65 @@ export async function startTestBackend<
...otherOptions
} = options;
+ let server: ExtendedHttpServer;
+
+ const rootHttpRouterFactory = createServiceFactory({
+ service: coreServices.rootHttpRouter,
+ deps: {
+ config: coreServices.config,
+ lifecycle: coreServices.rootLifecycle,
+ rootLogger: coreServices.rootLogger,
+ },
+ async factory({ config, lifecycle, rootLogger }) {
+ const router = DefaultRootHttpRouter.create();
+ const logger = rootLogger.child({ service: 'rootHttpRouter' });
+
+ const app = express();
+
+ const middleware = MiddlewareFactory.create({ config, logger });
+
+ app.use(router.handler());
+ app.use(middleware.notFound());
+ app.use(middleware.error());
+
+ server = await createHttpServer(
+ app,
+ { listen: { host: '', port: 0 } },
+ { logger },
+ );
+
+ lifecycle.addShutdownHook({
+ async fn() {
+ await server.stop();
+ },
+ logger,
+ });
+
+ await server.start();
+
+ return router;
+ },
+ });
+
+ const discoveryFactory = createServiceFactory({
+ service: coreServices.discovery,
+ deps: {
+ rootHttpRouter: coreServices.rootHttpRouter,
+ },
+ async factory() {
+ if (!server) {
+ throw new Error('Test server not started yet');
+ }
+ const port = server.port();
+ const discovery = SingleHostDiscovery.fromConfig(
+ new ConfigReader({
+ backend: { baseUrl: `http://localhost:${port}`, listen: { port } },
+ }),
+ );
+ return async () => discovery;
+ },
+ });
+
const factories = services.map(serviceDef => {
if (Array.isArray(serviceDef)) {
// if type is ExtensionPoint?
@@ -107,7 +204,7 @@ export async function startTestBackend<
const backend = createSpecializedBackend({
...otherOptions,
- services: factories,
+ services: [...factories, rootHttpRouterFactory, discoveryFactory],
});
backendInstancesToCleanUp.push(backend);
@@ -129,7 +226,14 @@ export async function startTestBackend<
await backend.start();
- return backend;
+ return Object.assign(backend, {
+ get server() {
+ if (!server) {
+ throw new Error('TestBackend server is not available');
+ }
+ return server;
+ },
+ });
}
let registered = false;
diff --git a/packages/backend-test-utils/src/next/wiring/index.ts b/packages/backend-test-utils/src/next/wiring/index.ts
index eb7b773e33..7c39474d4c 100644
--- a/packages/backend-test-utils/src/next/wiring/index.ts
+++ b/packages/backend-test-utils/src/next/wiring/index.ts
@@ -15,4 +15,4 @@
*/
export { startTestBackend } from './TestBackend';
-export type { TestBackendOptions } from './TestBackend';
+export type { TestBackend, TestBackendOptions } from './TestBackend';
diff --git a/packages/catalog-model/examples/components/shuffle-api-component.yaml b/packages/catalog-model/examples/components/shuffle-api-component.yaml
index 6328ebdf3b..afac3d472d 100644
--- a/packages/catalog-model/examples/components/shuffle-api-component.yaml
+++ b/packages/catalog-model/examples/components/shuffle-api-component.yaml
@@ -3,6 +3,9 @@ kind: Component
metadata:
name: shuffle-api
description: Shuffle API
+ labels:
+ goVersion: go1.15.3
+ category: music
tags:
- go
spec:
diff --git a/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx
index 2893cf10c9..71d6f295e7 100644
--- a/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx
+++ b/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx
@@ -36,9 +36,9 @@ export default {
export const InHeader = () => (
Standard breadcrumbs
-
+
Underlined pages are links. This should show a hierarchical relationship.
-
+
@@ -61,17 +61,17 @@ export const OutsideOfHeader = () => {
const open = Boolean(anchorEl);
return (
-
+
It might be the case that you want to keep your breadcrumbs outside of
the header. In that case, they should be positioned above the title of
the page.
-
+
Standard breadcrumbs
-
+
Underlined pages are links. This should show a hierarchical
relationship.
-
+
@@ -82,10 +82,10 @@ export const OutsideOfHeader = () => {
Hidden breadcrumbs
-
+
Use this when you have more than three breadcrumbs. When user clicks on
ellipses, expand the breadcrumbs out.
-
+
General Page
@@ -96,10 +96,10 @@ export const OutsideOfHeader = () => {
Layered breadcrumbs
-
+
Use this when you want to show alternative breadcrumbs on the same
hierarchical level.
-
+
diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts
index 1b21d43338..237428c3e9 100644
--- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts
+++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts
@@ -252,8 +252,12 @@ describe('DefaultAwsCredentialsManager', () => {
},
});
const provider = DefaultAwsCredentialsManager.fromConfig(minConfig);
- const awsCredentialProvider1 = await provider.getCredentialProvider({});
- const awsCredentialProvider2 = await provider.getCredentialProvider({});
+ const awsCredentialProvider1 = await provider.getCredentialProvider({
+ accountId: '123456789012',
+ });
+ const awsCredentialProvider2 = await provider.getCredentialProvider({
+ accountId: '123456789012',
+ });
expect(awsCredentialProvider1).toBe(awsCredentialProvider2);
expect(stsMock).toHaveReceivedCommandTimes(GetCallerIdentityCommand, 1);
@@ -374,7 +378,7 @@ describe('DefaultAwsCredentialsManager', () => {
arn: 'arn:aws:s3:::bucket_name',
});
- expect(awsCredentialProvider.accountId).toEqual('123456789012');
+ expect(awsCredentialProvider.accountId).toBeUndefined();
const creds = await awsCredentialProvider.sdkCredentialProvider();
expect(creds).toEqual({
@@ -387,7 +391,7 @@ describe('DefaultAwsCredentialsManager', () => {
const provider = DefaultAwsCredentialsManager.fromConfig(config);
const awsCredentialProvider = await provider.getCredentialProvider({});
- expect(awsCredentialProvider.accountId).toEqual('123456789012');
+ expect(awsCredentialProvider.accountId).toBeUndefined();
const creds = await awsCredentialProvider.sdkCredentialProvider();
expect(creds).toEqual({
@@ -400,7 +404,7 @@ describe('DefaultAwsCredentialsManager', () => {
const provider = DefaultAwsCredentialsManager.fromConfig(config);
const awsCredentialProvider = await provider.getCredentialProvider();
- expect(awsCredentialProvider.accountId).toEqual('123456789012');
+ expect(awsCredentialProvider.accountId).toBeUndefined();
const creds = await awsCredentialProvider.sdkCredentialProvider();
expect(creds).toEqual({
@@ -421,10 +425,11 @@ describe('DefaultAwsCredentialsManager', () => {
it('rejects main account that has invalid credentials', async () => {
stsMock.on(GetCallerIdentityCommand).rejects('No credentials found');
- const provider = DefaultAwsCredentialsManager.fromConfig(config);
- await expect(provider.getCredentialProvider({})).rejects.toThrow(
- /No credentials found/,
- );
+ const minConfig = new ConfigReader({});
+ const provider = DefaultAwsCredentialsManager.fromConfig(minConfig);
+ await expect(
+ provider.getCredentialProvider({ accountId: '123456789012' }),
+ ).rejects.toThrow(/No credentials found/);
});
});
});
diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts
index 7799f08b4d..12e1e2dffd 100644
--- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts
+++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts
@@ -221,7 +221,6 @@ export class DefaultAwsCredentialsManager implements AwsCredentialsManager {
): Promise {
// If no options provided, fall back to the main account
if (!opts) {
- await fillInAccountId(this.mainAccountCredentialProvider);
return this.mainAccountCredentialProvider;
}
@@ -235,7 +234,6 @@ export class DefaultAwsCredentialsManager implements AwsCredentialsManager {
// If the account ID was not provided (explicitly or in the ARN),
// fall back to the main account
if (!accountId) {
- await fillInAccountId(this.mainAccountCredentialProvider);
return this.mainAccountCredentialProvider;
}
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index 1f58f64796..d5fbf9f239 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -56,7 +56,6 @@
"@backstage/cli": "workspace:^",
"@backstage/types": "workspace:^",
"@types/supertest": "^2.0.8",
- "get-port": "^6.1.2",
"mock-fs": "^5.1.0",
"msw": "^0.49.0",
"node-fetch": "^2.6.7",
diff --git a/plugins/app-backend/src/service/appPlugin.test.ts b/plugins/app-backend/src/service/appPlugin.test.ts
index 80408694d6..7c57a5d685 100644
--- a/plugins/app-backend/src/service/appPlugin.test.ts
+++ b/plugins/app-backend/src/service/appPlugin.test.ts
@@ -17,18 +17,14 @@
import mockFs from 'mock-fs';
import { resolve as resolvePath } from 'path';
import fetch from 'node-fetch';
-import { coreServices } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { appPlugin } from './appPlugin';
import {
databaseFactory,
httpRouterFactory,
- rootHttpRouterFactory,
loggerFactory,
rootLoggerFactory,
} from '@backstage/backend-app-api';
-import { ConfigReader } from '@backstage/config';
-import getPort from 'get-port';
describe('appPlugin', () => {
beforeEach(() => {
@@ -48,23 +44,12 @@ describe('appPlugin', () => {
});
it('boots', async () => {
- const port = await getPort();
- await startTestBackend({
+ const { server } = await startTestBackend({
services: [
- [
- coreServices.config,
- new ConfigReader({
- backend: {
- listen: { port },
- database: { client: 'better-sqlite3', connection: ':memory:' },
- },
- }),
- ],
loggerFactory(),
rootLoggerFactory(),
databaseFactory(),
httpRouterFactory(),
- rootHttpRouterFactory(),
],
features: [
appPlugin({
@@ -75,12 +60,12 @@ describe('appPlugin', () => {
});
await expect(
- fetch(`http://localhost:${port}/api/app/derp.html`).then(res =>
+ fetch(`http://localhost:${server.port()}/api/app/derp.html`).then(res =>
res.text(),
),
).resolves.toBe('winning');
await expect(
- fetch(`http://localhost:${port}`).then(res => res.text()),
+ fetch(`http://localhost:${server.port()}`).then(res => res.text()),
).resolves.toBe('winning');
});
});
diff --git a/plugins/app-backend/src/service/appPlugin.ts b/plugins/app-backend/src/service/appPlugin.ts
index 2c0c73c71b..b12a8d6877 100644
--- a/plugins/app-backend/src/service/appPlugin.ts
+++ b/plugins/app-backend/src/service/appPlugin.ts
@@ -70,9 +70,9 @@ export type AppPluginOptions = {
* The App plugin is responsible for serving the frontend app bundle and static assets.
* @alpha
*/
-export const appPlugin = createBackendPlugin({
+export const appPlugin = createBackendPlugin((options: AppPluginOptions) => ({
id: 'app',
- register(env, options: AppPluginOptions) {
+ register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
@@ -101,4 +101,4 @@ export const appPlugin = createBackendPlugin({
},
});
},
-});
+}));
diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx
index 8f527a635d..a4d833fa15 100644
--- a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx
+++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx
@@ -28,7 +28,6 @@ import {
TestApiProvider,
} from '@backstage/test-utils';
import { setupServer } from 'msw/node';
-import { DateTime } from 'luxon';
import { siteMock } from '../../mocks/mocks';
import { AzureSitesOverviewTable } from './AzureSitesOverviewTable';
import { azureSiteApiRef } from '../../api';
@@ -72,15 +71,19 @@ describe('AzureSitesOverviewWidget', () => {
,
);
- expect(await rendered.findByText(siteMock.name)).toBeInTheDocument();
- expect(await rendered.findByText(siteMock.location)).toBeInTheDocument();
- expect(await rendered.findByText(siteMock.state)).toBeInTheDocument();
- expect(
- await rendered.findByText(
- DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString(
- DateTime.DATETIME_MED,
- ),
- ),
- ).toBeInTheDocument();
+ await expect(
+ rendered.findByText(siteMock.name),
+ ).resolves.toBeInTheDocument();
+ expect(rendered.getByText(siteMock.location)).toBeInTheDocument();
+ expect(rendered.getByText(siteMock.state)).toBeInTheDocument();
+ // TODO(Rugvip): This check is disabled, because in Node.js 18.13 an unexpected
+ // invisible whitespace character is present in the formatted time.
+ // expect(
+ // rendered.getByText(
+ // DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString(
+ // DateTime.DATETIME_MED,
+ // ),
+ // ),
+ // ).toBeInTheDocument();
});
});
diff --git a/plugins/bazaar/src/components/EntityBazaarInfoContent/EntityBazaarInfoContent.tsx b/plugins/bazaar/src/components/EntityBazaarInfoContent/EntityBazaarInfoContent.tsx
index a2e5fe151b..e0a3a9b881 100644
--- a/plugins/bazaar/src/components/EntityBazaarInfoContent/EntityBazaarInfoContent.tsx
+++ b/plugins/bazaar/src/components/EntityBazaarInfoContent/EntityBazaarInfoContent.tsx
@@ -16,6 +16,7 @@
import React, { useState, useEffect } from 'react';
import { CardHeader, Divider, IconButton, makeStyles } from '@material-ui/core';
+import Typography from '@material-ui/core/Typography';
import {
HeaderIconLinkRow,
IconLinkVerticalProps,
@@ -182,7 +183,11 @@ export const EntityBazaarInfoContent = ({
)}
{bazaarProject?.title!}
}
+ title={
+
+ {bazaarProject?.title!}
+
+ }
action={
+
{bazaarProject.value?.title || initProject.title}
-
+
}
action={
diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md
index a911fb5ebb..63b85f223b 100644
--- a/plugins/catalog-backend-module-aws/api-report.md
+++ b/plugins/catalog-backend-module-aws/api-report.md
@@ -90,7 +90,5 @@ export class AwsS3EntityProvider implements EntityProvider {
}
// @alpha
-export const awsS3EntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const awsS3EntityProviderCatalogModule: () => BackendFeature;
```
diff --git a/plugins/catalog-backend-module-azure/api-report.md b/plugins/catalog-backend-module-azure/api-report.md
index 84ffaf39e9..5ffae14b6b 100644
--- a/plugins/catalog-backend-module-azure/api-report.md
+++ b/plugins/catalog-backend-module-azure/api-report.md
@@ -58,7 +58,5 @@ export class AzureDevOpsEntityProvider implements EntityProvider {
}
// @alpha
-export const azureDevOpsEntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const azureDevOpsEntityProviderCatalogModule: () => BackendFeature;
```
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md
index 480395e477..7ad6b3b3e0 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md
+++ b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md
@@ -48,7 +48,5 @@ export class BitbucketCloudEntityProvider
}
// @alpha (undocumented)
-export const bitbucketCloudEntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const bitbucketCloudEntityProviderCatalogModule: () => BackendFeature;
```
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts
index 83e657bcfe..d44281600c 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts
+++ b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-import { ConfigReader } from '@backstage/config';
-import {
- getVoidLogger,
- PluginEndpointDiscovery,
- TokenManager,
-} from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
+import {
+ startTestBackend,
+ mockConfigFactory,
+} from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
import { Duration } from 'luxon';
@@ -55,22 +52,6 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
return runner;
},
} as unknown as PluginTaskScheduler;
- const discovery = jest.fn() as any as PluginEndpointDiscovery;
- const tokenManager = jest.fn() as any as TokenManager;
-
- const config = new ConfigReader({
- catalog: {
- providers: {
- bitbucketCloud: {
- schedule: {
- frequency: 'P1M',
- timeout: 'PT3M',
- },
- workspace: 'test-ws',
- },
- },
- },
- });
await startTestBackend({
extensionPoints: [
@@ -78,11 +59,22 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
[eventsExtensionPoint, eventsExtensionPointImpl],
],
services: [
- [coreServices.config, config],
- [coreServices.discovery, discovery],
- [coreServices.logger, getVoidLogger()],
+ mockConfigFactory({
+ data: {
+ catalog: {
+ providers: {
+ bitbucketCloud: {
+ schedule: {
+ frequency: 'P1M',
+ timeout: 'PT3M',
+ },
+ workspace: 'test-ws',
+ },
+ },
+ },
+ },
+ }),
[coreServices.scheduler, scheduler],
- [coreServices.tokenManager, tokenManager],
],
features: [bitbucketCloudEntityProviderCatalogModule()],
});
diff --git a/plugins/catalog-backend-module-bitbucket-server/api-report.md b/plugins/catalog-backend-module-bitbucket-server/api-report.md
index a38a4d8c35..213f5d2573 100644
--- a/plugins/catalog-backend-module-bitbucket-server/api-report.md
+++ b/plugins/catalog-backend-module-bitbucket-server/api-report.md
@@ -69,9 +69,7 @@ export class BitbucketServerEntityProvider implements EntityProvider {
}
// @alpha (undocumented)
-export const bitbucketServerEntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const bitbucketServerEntityProviderCatalogModule: () => BackendFeature;
// @public (undocumented)
export type BitbucketServerListOptions = {
diff --git a/plugins/catalog-backend-module-gerrit/api-report.md b/plugins/catalog-backend-module-gerrit/api-report.md
index 45847ea93d..870937f7ec 100644
--- a/plugins/catalog-backend-module-gerrit/api-report.md
+++ b/plugins/catalog-backend-module-gerrit/api-report.md
@@ -31,9 +31,7 @@ export class GerritEntityProvider implements EntityProvider {
}
// @alpha (undocumented)
-export const gerritEntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const gerritEntityProviderCatalogModule: () => BackendFeature;
// (No @packageDocumentation comment for this package)
```
diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md
index 78ffd5b018..a66ec712c7 100644
--- a/plugins/catalog-backend-module-github/api-report.md
+++ b/plugins/catalog-backend-module-github/api-report.md
@@ -101,9 +101,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber {
}
// @alpha
-export const githubEntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const githubEntityProviderCatalogModule: () => BackendFeature;
// @public (undocumented)
export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md
index 9ed90a2c42..36713d2427 100644
--- a/plugins/catalog-backend-module-gitlab/api-report.md
+++ b/plugins/catalog-backend-module-gitlab/api-report.md
@@ -34,9 +34,7 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
}
// @alpha
-export const gitlabDiscoveryEntityProviderCatalogModule: (
- options?: undefined,
-) => BackendFeature;
+export const gitlabDiscoveryEntityProviderCatalogModule: () => BackendFeature;
// @public
export class GitLabDiscoveryProcessor implements CatalogProcessor {
diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json
index 06a0ab1c89..a86e27fae4 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/package.json
+++ b/plugins/catalog-backend-module-incremental-ingestion/package.json
@@ -56,8 +56,7 @@
"devDependencies": {
"@backstage/backend-app-api": "workspace:^",
"@backstage/cli": "workspace:^",
- "@backstage/plugin-catalog-backend": "workspace:^",
- "get-port": "^6.1.2"
+ "@backstage/plugin-catalog-backend": "workspace:^"
},
"files": [
"alpha",
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/incrementalIngestionEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/incrementalIngestionEntityProviderCatalogModule.ts
index 0ecb45292c..b173b2d2ef 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/incrementalIngestionEntityProviderCatalogModule.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/incrementalIngestionEntityProviderCatalogModule.ts
@@ -31,51 +31,50 @@ import { WrapperProviders } from './WrapperProviders';
* @alpha
*/
export const incrementalIngestionEntityProviderCatalogModule =
- createBackendModule({
- pluginId: 'catalog',
- moduleId: 'incrementalIngestionEntityProvider',
- register(
- env,
- options: {
- providers: Array<{
- provider: IncrementalEntityProvider
;
- options: IncrementalEntityProviderOptions;
- }>;
- },
- ) {
- env.registerInit({
- deps: {
- catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
- database: coreServices.database,
- httpRouter: coreServices.httpRouter,
- logger: coreServices.logger,
- scheduler: coreServices.scheduler,
- },
- async init({
- catalog,
- config,
- database,
- httpRouter,
- logger,
- scheduler,
- }) {
- const client = await database.getClient();
-
- const providers = new WrapperProviders({
+ createBackendModule(
+ (options: {
+ providers: Array<{
+ provider: IncrementalEntityProvider;
+ options: IncrementalEntityProviderOptions;
+ }>;
+ }) => ({
+ pluginId: 'catalog',
+ moduleId: 'incrementalIngestionEntityProvider',
+ register(env) {
+ env.registerInit({
+ deps: {
+ catalog: catalogProcessingExtensionPoint,
+ config: coreServices.config,
+ database: coreServices.database,
+ httpRouter: coreServices.httpRouter,
+ logger: coreServices.logger,
+ scheduler: coreServices.scheduler,
+ },
+ async init({
+ catalog,
config,
+ database,
+ httpRouter,
logger,
- client,
scheduler,
- });
+ }) {
+ const client = await database.getClient();
- for (const entry of options.providers) {
- const wrapped = providers.wrap(entry.provider, entry.options);
- catalog.addEntityProvider(wrapped);
- }
+ const providers = new WrapperProviders({
+ config,
+ logger,
+ client,
+ scheduler,
+ });
- httpRouter.use(await providers.adminRouter());
- },
- });
- },
- });
+ for (const entry of options.providers) {
+ const wrapped = providers.wrap(entry.provider, entry.options);
+ catalog.addEntityProvider(wrapped);
+ }
+
+ httpRouter.use(await providers.adminRouter());
+ },
+ });
+ },
+ }),
+ );
diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md
index 921d682b89..a5abdc17c6 100644
--- a/plugins/catalog-backend-module-msgraph/api-report.md
+++ b/plugins/catalog-backend-module-msgraph/api-report.md
@@ -135,9 +135,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
}
// @alpha
-export const microsoftGraphOrgEntityProviderCatalogModule: (
- options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions | undefined,
-) => BackendFeature;
+export const microsoftGraphOrgEntityProviderCatalogModule: () => BackendFeature;
// @alpha
export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions {
diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md
index 3e6c5ef676..6ff9ac4b1f 100644
--- a/plugins/catalog-backend/api-report.md
+++ b/plugins/catalog-backend/api-report.md
@@ -237,7 +237,7 @@ export type CatalogPermissionRule<
> = PermissionRule;
// @alpha
-export const catalogPlugin: (options?: undefined) => BackendFeature;
+export const catalogPlugin: () => BackendFeature;
// @public
export interface CatalogProcessingEngine {
diff --git a/plugins/catalog-node/src/catalogService.test.ts b/plugins/catalog-node/src/catalogService.test.ts
index 230af79a88..ec3e5ccf1b 100644
--- a/plugins/catalog-node/src/catalogService.test.ts
+++ b/plugins/catalog-node/src/catalogService.test.ts
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-import { PluginEndpointDiscovery } from '@backstage/backend-common';
-import {
- createBackendModule,
- coreServices,
-} from '@backstage/backend-plugin-api';
+import { createBackendModule } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { CatalogClient } from '@backstage/catalog-client';
import { catalogServiceRef } from './catalogService';
@@ -42,9 +38,6 @@ describe('catalogServiceRef', () => {
});
await startTestBackend({
- services: [
- [coreServices.discovery, {} as unknown as PluginEndpointDiscovery],
- ],
features: [testModule()],
});
});
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx
index 2fcb89eed1..16804426fb 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { IconButton } from '@material-ui/core';
import EmailIcon from '@material-ui/icons/Email';
import React from 'react';
@@ -23,14 +24,13 @@ import { Link } from '@backstage/core-components';
*
* @private
*/
-export const EmailCardAction = ({ email }: { email: string }) => {
+export const EmailCardAction = (props: { email: string }) => {
return (
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx
index fc8b5f4007..3f8f67c668 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { entityRouteRef } from '../../../routes';
import { IconButton } from '@material-ui/core';
import InfoIcon from '@material-ui/icons/Info';
import React from 'react';
import { useRouteRef } from '@backstage/core-plugin-api';
-import { Entity } from '@backstage/catalog-model';
+import { Entity, getCompoundEntityRef } from '@backstage/catalog-model';
import { Link } from '@backstage/core-components';
/**
@@ -26,7 +27,7 @@ import { Link } from '@backstage/core-components';
*
* @private
*/
-export const EntityCardActions = ({ entity }: { entity: Entity }) => {
+export const EntityCardActions = (props: { entity: Entity }) => {
const entityRoute = useRouteRef(entityRouteRef);
return (
@@ -34,11 +35,7 @@ export const EntityCardActions = ({ entity }: { entity: Entity }) => {
component={Link}
aria-label="Show"
title="Show details"
- to={entityRoute({
- name: entity.metadata.name,
- namespace: entity.metadata.namespace || 'default',
- kind: entity.kind.toLocaleLowerCase('en-US'),
- })}
+ to={entityRoute(getCompoundEntityRef(props.entity))}
>
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx
index fca0763703..9bc840c112 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { EmailCardAction } from './EmailCardAction';
import React from 'react';
import { GroupEntity } from '@backstage/catalog-model';
@@ -22,12 +23,7 @@ import { GroupEntity } from '@backstage/catalog-model';
*
* @private
*/
-export const GroupCardActions = ({ entity }: { entity: GroupEntity }) => {
- return (
- <>
- {entity.spec.profile?.email && (
-
- )}
- >
- );
+export const GroupCardActions = (props: { entity: GroupEntity }) => {
+ const email = props.entity.spec.profile?.email;
+ return email ? : null;
};
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx
index 57801497f6..cdf0eea385 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { EmailCardAction } from './EmailCardAction';
import React from 'react';
import { UserEntity } from '@backstage/catalog-model';
@@ -22,12 +23,7 @@ import { UserEntity } from '@backstage/catalog-model';
*
* @private
*/
-export const UserCardActions = ({ entity }: { entity: UserEntity }) => {
- return (
- <>
- {entity.spec.profile?.email && (
-
- )}
- >
- );
+export const UserCardActions = (props: { entity: UserEntity }) => {
+ const email = props.entity.spec.profile?.email;
+ return email ? : null;
};
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts
index 75959026f7..ba2114d73d 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
export { EntityCardActions } from './EntityCardActions';
export { GroupCardActions } from './GroupCardActions';
export { UserCardActions } from './UserCardActions';
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx
index 132042452b..bee5587ea1 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx
@@ -33,12 +33,8 @@ import { Table, TableColumn } from '@backstage/core-components';
import { EntityRefLink } from '../EntityRefLink';
const mockCatalogApi = {
- getEntityByRef: async (entityRef: CompoundEntityRef) => {
- if (
- entityRef.namespace === 'default' &&
- entityRef.name === 'playback' &&
- entityRef.kind === 'component'
- ) {
+ getEntityByRef: async (entityRef: string) => {
+ if (entityRef === 'component:default/playback') {
return {
kind: 'Component',
metadata: {
@@ -48,11 +44,7 @@ const mockCatalogApi = {
},
};
}
- if (
- entityRef.namespace === 'default' &&
- entityRef.name === 'fname.lname' &&
- entityRef.kind === 'user'
- ) {
+ if (entityRef === 'user:default/fname.lname') {
return {
kind: 'User',
metadata: {
@@ -66,11 +58,7 @@ const mockCatalogApi = {
},
};
}
- if (
- entityRef.namespace === 'default' &&
- entityRef.name === 'slow.catalog.item' &&
- entityRef.kind === 'component'
- ) {
+ if (entityRef === 'component:default/slow.catalog.item') {
await new Promise(resolve => setTimeout(resolve, 3000));
return {
kind: 'Component',
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx
index 2fc630313f..f665ca6c00 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx
@@ -14,26 +14,21 @@
* limitations under the License.
*/
-import { fireEvent, screen } from '@testing-library/react';
+import { screen } from '@testing-library/react';
+import user from '@testing-library/user-event';
import React from 'react';
import { EntityPeekAheadPopover } from './EntityPeekAheadPopover';
import { ApiProvider } from '@backstage/core-app-api';
import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils';
import { catalogApiRef } from '../../api';
-import { CompoundEntityRef, Entity } from '@backstage/catalog-model';
+import { Entity } from '@backstage/catalog-model';
import { CatalogApi } from '@backstage/catalog-client';
import { Button } from '@material-ui/core';
import { entityRouteRef } from '../../routes';
const catalogApi: Partial = {
- getEntityByRef: async (
- entityRef: CompoundEntityRef,
- ): Promise => {
- if (
- entityRef.name === 'service1' &&
- entityRef.namespace === 'default' &&
- entityRef.kind === 'component'
- ) {
+ getEntityByRef: async (entityRef: string): Promise => {
+ if (entityRef === 'component:default/service1') {
return {
apiVersion: '',
kind: 'Component',
@@ -71,14 +66,14 @@ describe(' ', () => {
);
expect(screen.getByText('s1')).toBeInTheDocument();
expect(screen.queryByText('service1')).toBeNull();
- fireEvent.mouseOver(screen.getByTestId('popover1'));
+ user.hover(screen.getByTestId('popover1'));
expect(await screen.findByText('service1')).toBeInTheDocument();
expect(screen.getByText('s2')).toBeInTheDocument();
expect(screen.queryByText('service2')).toBeNull();
- fireEvent.mouseOver(screen.getByTestId('popover2'));
+ user.hover(screen.getByTestId('popover2'));
expect(
- await screen.findByText('Error: service2 was not found'),
+ await screen.findByText('Error: component:default/service2 not found'),
).toBeInTheDocument();
});
});
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx
index d6c1e6e59a..91d60d4f13 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { catalogApiRef } from '../../api';
-import React, { PropsWithChildren, useEffect, useState } from 'react';
+import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react';
import HoverPopover from 'material-ui-popup-state/HoverPopover';
import {
bindHover,
@@ -33,11 +34,7 @@ import {
Typography,
} from '@material-ui/core';
import { useApiHolder } from '@backstage/core-plugin-api';
-import {
- isGroupEntity,
- isUserEntity,
- parseEntityRef,
-} from '@backstage/catalog-model';
+import { isGroupEntity, isUserEntity } from '@backstage/catalog-model';
import { Progress, ResponseErrorPanel } from '@backstage/core-components';
import {
EntityCardActions,
@@ -58,9 +55,6 @@ export type EntityPeekAheadPopoverProps = PropsWithChildren<{
const useStyles = makeStyles(() => {
return {
- trigger: {
- display: 'inline-block',
- },
popoverPaper: {
width: '30em',
},
@@ -90,27 +84,24 @@ export const EntityPeekAheadPopover = (props: EntityPeekAheadPopoverProps) => {
variant: 'popover',
popupId: 'entity-peek-ahead',
});
- const compoundEntityRef = parseEntityRef(entityRef);
const [isHovered, setIsHovered] = useState(false);
- const debouncedHandleMouseEnter = debounce(
- () => setIsHovered(true),
- delayTime,
+ const debouncedHandleMouseEnter = useMemo(
+ () => debounce(() => setIsHovered(true), delayTime),
+ [delayTime],
);
const [{ loading, error, value: entity }, load] = useAsyncFn(async () => {
const catalogApi = apiHolder.get(catalogApiRef);
if (catalogApi) {
- const retrievedEntity = await catalogApi.getEntityByRef(
- compoundEntityRef,
- );
+ const retrievedEntity = await catalogApi.getEntityByRef(entityRef);
if (!retrievedEntity) {
- throw new Error(`${compoundEntityRef.name} was not found`);
+ throw new Error(`${entityRef} not found`);
}
return retrievedEntity;
}
return undefined;
- }, [apiHolder, compoundEntityRef]);
+ }, [apiHolder, entityRef]);
const handleOnMouseLeave = () => {
setIsHovered(false);
@@ -150,61 +141,58 @@ export const EntityPeekAheadPopover = (props: EntityPeekAheadPopoverProps) => {
}}
onMouseLeave={handleOnMouseLeave}
>
- <>
- {error && }
-
+
+
+ {error && }
{loading && }
-
-
- {entity && (
- <>
-
- {compoundEntityRef.namespace}
-
-
- {compoundEntityRef.name}
-
- {entity.kind}
+ {entity && (
+ <>
+
+ {entity.metadata.namespace}
+
+
+ {entity.metadata.name}
+
+
+ {entity.kind}
+
+ {entity.metadata.description && (
{entity.metadata.description}
- {entity.spec?.type}
-
- {(entity.metadata.tags || [])
- .slice(0, maxTagChips)
- .map(tag => {
- return ;
- })}
- {entity.metadata.tags?.length &&
- entity.metadata.tags?.length > maxTagChips && (
-
-
-
- )}
-
- >
- )}
-
- {!error && (
-
- {entity && (
- <>
- {isUserEntity(entity) && (
-
- )}
- {isGroupEntity(entity) && (
-
- )}
-
- >
)}
-
+ {entity.spec?.type}
+
+ {(entity.metadata.tags || [])
+ .slice(0, maxTagChips)
+ .map(tag => {
+ return ;
+ })}
+ {entity.metadata.tags?.length &&
+ entity.metadata.tags?.length > maxTagChips && (
+
+
+
+ )}
+
+ >
)}
-
- >
+
+ {!error && entity && (
+
+ <>
+ {isUserEntity(entity) && }
+ {isGroupEntity(entity) && (
+
+ )}
+
+ >
+
+ )}
+
)}
>
diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md
index 74f64882a8..82df851239 100644
--- a/plugins/catalog/api-report.md
+++ b/plugins/catalog/api-report.md
@@ -277,6 +277,17 @@ export const EntityHasSubcomponentsCard: (
// @public (undocumented)
export const EntityHasSystemsCard: (props: HasSystemsCardProps) => JSX.Element;
+// @public (undocumented)
+export const EntityLabelsCard: (props: EntityLabelsCardProps) => JSX.Element;
+
+// @public (undocumented)
+export interface EntityLabelsCardProps {
+ // (undocumented)
+ title?: string;
+ // (undocumented)
+ variant?: InfoCardVariants;
+}
+
// @public
export const EntityLayout: {
(props: EntityLayoutProps): JSX.Element;
@@ -385,6 +396,9 @@ export interface HasComponentsCardProps {
variant?: InfoCardVariants;
}
+// @public
+export function hasLabels(entity: Entity): boolean;
+
// @public (undocumented)
export interface HasResourcesCardProps {
// (undocumented)
diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx
new file mode 100644
index 0000000000..500c651cc0
--- /dev/null
+++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 { useEntity } from '@backstage/plugin-catalog-react';
+import React from 'react';
+import {
+ InfoCard,
+ InfoCardVariants,
+ Table,
+ TableColumn,
+} from '@backstage/core-components';
+import { EntityLabelsEmptyState } from './EntityLabelsEmptyState';
+import { makeStyles, Typography } from '@material-ui/core';
+
+/** @public */
+export interface EntityLabelsCardProps {
+ variant?: InfoCardVariants;
+ title?: string;
+}
+
+const useStyles = makeStyles(_ => ({
+ key: {
+ fontWeight: 'bold',
+ },
+}));
+
+export const EntityLabelsCard = (props: EntityLabelsCardProps) => {
+ const { variant, title } = props;
+ const { entity } = useEntity();
+ const classes = useStyles();
+
+ const columns: TableColumn<{ key: string; value: string }>[] = [
+ {
+ render: row => {
+ return (
+
+ {row.key}
+
+ );
+ },
+ },
+ {
+ field: 'value',
+ },
+ ];
+
+ const labels = entity?.metadata?.labels;
+
+ return (
+
+ {!labels || Object.keys(labels).length === 0 ? (
+
+ ) : (
+ ({
+ key: labelKey,
+ value: labels[labelKey],
+ }))}
+ options={{
+ search: false,
+ showTitle: true,
+ loadingType: 'linear',
+ header: false,
+ padding: 'dense',
+ pageSize: 5,
+ toolbar: false,
+ paging: Object.keys(labels).length > 5,
+ }}
+ />
+ )}
+
+ );
+};
diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx
new file mode 100644
index 0000000000..a875f542a7
--- /dev/null
+++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx
@@ -0,0 +1,66 @@
+/*
+ * 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 { BackstageTheme } from '@backstage/theme';
+import { Button, makeStyles, Typography } from '@material-ui/core';
+import React from 'react';
+import { CodeSnippet } from '@backstage/core-components';
+
+const ENTITY_YAML = `metadata:
+ name: example
+ labels:
+ javaVersion: 1.2.3`;
+
+const useStyles = makeStyles(
+ theme => ({
+ code: {
+ borderRadius: 6,
+ margin: `${theme.spacing(2)}px 0px`,
+ background: theme.palette.type === 'dark' ? '#444' : '#fff',
+ },
+ }),
+ { name: 'PluginCatalogEntityLabelsEmptyState' },
+);
+
+export function EntityLabelsEmptyState() {
+ const classes = useStyles();
+
+ return (
+ <>
+
+ No labels defined for this entity. You can add labels to your entity
+ YAML as shown in the highlighted example below:
+
+
+
+
+
+ Read more
+
+ >
+ );
+}
diff --git a/packages/backend-plugin-api/src/types.ts b/plugins/catalog/src/components/EntityLabelsCard/conditions.ts
similarity index 61%
rename from packages/backend-plugin-api/src/types.ts
rename to plugins/catalog/src/components/EntityLabelsCard/conditions.ts
index 6da3bafe17..fb4678cded 100644
--- a/packages/backend-plugin-api/src/types.ts
+++ b/plugins/catalog/src/components/EntityLabelsCard/conditions.ts
@@ -14,19 +14,16 @@
* limitations under the License.
*/
-/**
- * Base type for options objects that aren't required.
- *
- * @ignore
- */
-export type MaybeOptions = object | undefined;
+import { Entity } from '@backstage/catalog-model';
/**
- * Helper type that makes the options argument optional if options are not required.
+ * Returns true if the given entity has labels annotation given by the
+ * catalog. For use by EntitySwitch
*
- * @ignore
+ * @public
*/
-export type FactoryFunctionWithOptions =
- undefined extends TOptions
- ? (options?: TOptions) => TResult
- : (options: TOptions) => TResult;
+export function hasLabels(entity: Entity) {
+ return entity?.metadata?.labels
+ ? Object.keys(entity?.metadata?.labels).some(Boolean)
+ : false;
+}
diff --git a/plugins/catalog/src/components/EntityLabelsCard/index.ts b/plugins/catalog/src/components/EntityLabelsCard/index.ts
new file mode 100644
index 0000000000..870819f9a7
--- /dev/null
+++ b/plugins/catalog/src/components/EntityLabelsCard/index.ts
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+export { EntityLabelsCard } from './EntityLabelsCard';
+export * from './conditions';
+export type { EntityLabelsCardProps } from './EntityLabelsCard';
diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts
index 886039e329..7de332cf51 100644
--- a/plugins/catalog/src/index.ts
+++ b/plugins/catalog/src/index.ts
@@ -37,6 +37,7 @@ export * from './components/EntityProcessingErrorsPanel';
export * from './components/EntitySwitch';
export * from './components/FilteredEntityLayout';
export * from './overridableComponents';
+export * from './components/EntityLabelsCard';
export {
CatalogEntityPage,
CatalogIndexPage,
@@ -50,6 +51,7 @@ export {
EntityHasSubcomponentsCard,
EntityHasSystemsCard,
EntityLinksCard,
+ EntityLabelsCard,
RelatedEntitiesCard,
} from './plugin';
diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts
index 5419854cb6..c83657b85a 100644
--- a/plugins/catalog/src/plugin.ts
+++ b/plugins/catalog/src/plugin.ts
@@ -126,6 +126,17 @@ export const EntityLinksCard = catalogPlugin.provide(
}),
);
+/** @public */
+export const EntityLabelsCard = catalogPlugin.provide(
+ createComponentExtension({
+ name: 'EntityLabelsCard',
+ component: {
+ lazy: () =>
+ import('./components/EntityLabelsCard').then(m => m.EntityLabelsCard),
+ },
+ }),
+);
+
/** @public */
export const EntityHasSystemsCard: (props: HasSystemsCardProps) => JSX.Element =
catalogPlugin.provide(
diff --git a/plugins/events-backend-module-aws-sqs/api-report.md b/plugins/events-backend-module-aws-sqs/api-report.md
index 79a422c9f3..ede18f2787 100644
--- a/plugins/events-backend-module-aws-sqs/api-report.md
+++ b/plugins/events-backend-module-aws-sqs/api-report.md
@@ -23,7 +23,5 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher {
}
// @alpha
-export const awsSqsConsumingEventPublisherEventsModule: (
- options?: undefined,
-) => BackendFeature;
+export const awsSqsConsumingEventPublisherEventsModule: () => BackendFeature;
```
diff --git a/plugins/events-backend-module-azure/api-report.md b/plugins/events-backend-module-azure/api-report.md
index 510ec91c47..2e13a7ed4a 100644
--- a/plugins/events-backend-module-azure/api-report.md
+++ b/plugins/events-backend-module-azure/api-report.md
@@ -15,7 +15,5 @@ export class AzureDevOpsEventRouter extends SubTopicEventRouter {
}
// @alpha
-export const azureDevOpsEventRouterEventsModule: (
- options?: undefined,
-) => BackendFeature;
+export const azureDevOpsEventRouterEventsModule: () => BackendFeature;
```
diff --git a/plugins/events-backend-module-bitbucket-cloud/api-report.md b/plugins/events-backend-module-bitbucket-cloud/api-report.md
index 0e4acfac91..c47252ce17 100644
--- a/plugins/events-backend-module-bitbucket-cloud/api-report.md
+++ b/plugins/events-backend-module-bitbucket-cloud/api-report.md
@@ -15,7 +15,5 @@ export class BitbucketCloudEventRouter extends SubTopicEventRouter {
}
// @alpha
-export const bitbucketCloudEventRouterEventsModule: (
- options?: undefined,
-) => BackendFeature;
+export const bitbucketCloudEventRouterEventsModule: () => BackendFeature;
```
diff --git a/plugins/events-backend-module-gerrit/api-report.md b/plugins/events-backend-module-gerrit/api-report.md
index 268e5970fa..c8d4a7c9b6 100644
--- a/plugins/events-backend-module-gerrit/api-report.md
+++ b/plugins/events-backend-module-gerrit/api-report.md
@@ -15,7 +15,5 @@ export class GerritEventRouter extends SubTopicEventRouter {
}
// @alpha
-export const gerritEventRouterEventsModule: (
- options?: undefined,
-) => BackendFeature;
+export const gerritEventRouterEventsModule: () => BackendFeature;
```
diff --git a/plugins/events-backend-module-github/api-report.md b/plugins/events-backend-module-github/api-report.md
index b2a4425ae6..4f14bce789 100644
--- a/plugins/events-backend-module-github/api-report.md
+++ b/plugins/events-backend-module-github/api-report.md
@@ -22,10 +22,8 @@ export class GithubEventRouter extends SubTopicEventRouter {
}
// @alpha
-export const githubEventRouterEventsModule: (
- options?: undefined,
-) => BackendFeature;
+export const githubEventRouterEventsModule: () => BackendFeature;
// @alpha
-export const githubWebhookEventsModule: (options?: undefined) => BackendFeature;
+export const githubWebhookEventsModule: () => BackendFeature;
```
diff --git a/plugins/events-backend-module-gitlab/api-report.md b/plugins/events-backend-module-gitlab/api-report.md
index 8f130902a2..2a88295032 100644
--- a/plugins/events-backend-module-gitlab/api-report.md
+++ b/plugins/events-backend-module-gitlab/api-report.md
@@ -20,10 +20,8 @@ export class GitlabEventRouter extends SubTopicEventRouter {
}
// @alpha
-export const gitlabEventRouterEventsModule: (
- options?: undefined,
-) => BackendFeature;
+export const gitlabEventRouterEventsModule: () => BackendFeature;
// @alpha
-export const gitlabWebhookEventsModule: (options?: undefined) => BackendFeature;
+export const gitlabWebhookEventsModule: () => BackendFeature;
```
diff --git a/plugins/events-backend/api-report.md b/plugins/events-backend/api-report.md
index 69d7600ed3..28437772ae 100644
--- a/plugins/events-backend/api-report.md
+++ b/plugins/events-backend/api-report.md
@@ -29,7 +29,7 @@ export class EventsBackend {
}
// @alpha
-export const eventsPlugin: (options?: undefined) => BackendFeature;
+export const eventsPlugin: () => BackendFeature;
// @public
export class HttpPostIngressEventPublisher implements EventPublisher {
diff --git a/plugins/fossa/src/components/FossaCard/FossaCard.tsx b/plugins/fossa/src/components/FossaCard/FossaCard.tsx
index 4c51cdeb42..c31f5b9d0c 100644
--- a/plugins/fossa/src/components/FossaCard/FossaCard.tsx
+++ b/plugins/fossa/src/components/FossaCard/FossaCard.tsx
@@ -166,7 +166,8 @@ export const FossaCard = (props: { variant?: InfoCardVariants }) => {
spacing={0}
>
- 0 || value.dependencyCount === 0
? classes.numberError
@@ -174,16 +175,18 @@ export const FossaCard = (props: { variant?: InfoCardVariants }) => {
}
>
{value.issueCount}
-
+
{value.dependencyCount > 0 && (
- Number of issues
+
+ Number of issues
+
)}
{value.dependencyCount === 0 && (
-
+
No Dependencies.
Please check your FOSSA project settings.
-
+
)}
diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx
index 88f8567b90..f05a41e71b 100644
--- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx
+++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx
@@ -68,8 +68,12 @@ const generatedColumns: TableColumn[] = [
title: 'Source',
render: (row: Partial) => (
- {row.source?.branchName}
- {row.source?.commit.hash}
+
+ {row.source?.branchName}
+
+
+ {row.source?.commit.hash}
+
),
},
diff --git a/plugins/home/src/homePageComponents/RandomJoke/Content.tsx b/plugins/home/src/homePageComponents/RandomJoke/Content.tsx
index 231e32356a..80467c46fa 100644
--- a/plugins/home/src/homePageComponents/RandomJoke/Content.tsx
+++ b/plugins/home/src/homePageComponents/RandomJoke/Content.tsx
@@ -14,18 +14,19 @@
* limitations under the License.
*/
+import Typography from '@material-ui/core/Typography';
import React from 'react';
import { useRandomJoke } from './Context';
export const Content = () => {
const { joke, loading } = useRandomJoke();
- if (loading) return Loading...
;
+ if (loading) return Loading... ;
return (
-
{joke.setup}
-
{joke.punchline}
+
{joke.setup}
+
{joke.punchline}
);
};
diff --git a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
index c525e297be..0217f00513 100644
--- a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
+++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
@@ -126,12 +126,12 @@ const generatedColumns: TableColumn[] = [
field: 'lastBuild.source.branchName',
render: (row: Partial) => (
<>
-
+
{row.lastBuild?.source?.branchName}
-
- {row.lastBuild?.source?.commit?.hash}
+
+ {row.lastBuild?.source?.commit?.hash}
>
),
},
@@ -152,7 +152,7 @@ const generatedColumns: TableColumn[] = [
render: (row: Partial) => {
return (
<>
-
+
{row.lastBuild?.tests && (
{row.lastBuild?.tests.passed} / {row.lastBuild?.tests.total}{' '}
@@ -165,7 +165,7 @@ const generatedColumns: TableColumn[] = [
)}
{!row.lastBuild?.tests && 'n/a'}
-
+
>
);
},
diff --git a/plugins/kubernetes/src/components/CustomResources/ArgoRollouts/StepsProgress.tsx b/plugins/kubernetes/src/components/CustomResources/ArgoRollouts/StepsProgress.tsx
index ea9c37aa64..c93ab41d1d 100644
--- a/plugins/kubernetes/src/components/CustomResources/ArgoRollouts/StepsProgress.tsx
+++ b/plugins/kubernetes/src/components/CustomResources/ArgoRollouts/StepsProgress.tsx
@@ -16,6 +16,7 @@
import React from 'react';
import { Step, StepLabel, Stepper } from '@material-ui/core';
+import Typography from '@material-ui/core/Typography';
import {
ArgoRolloutCanaryStep,
SetWeightStep,
@@ -49,11 +50,11 @@ const createLabelForStep = (step: ArgoRolloutCanaryStep): React.ReactNode => {
} else if (isAnalysisStep(step)) {
return (
-
analysis templates:
+
analysis templates:
{step.analysis.templates.map((t, i) => (
-
{`${t.templateName}${
+ {`${t.templateName}${
t.clusterScope ? ' (cluster scoped)' : ''
- }`}
+ }`}
))}
);
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index c89ead6d2b..21026c203c 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -625,7 +625,7 @@ export type RunCommandOptions = {
};
// @alpha
-export const scaffolderCatalogModule: (options?: undefined) => BackendFeature;
+export const scaffolderCatalogModule: () => BackendFeature;
// @public (undocumented)
export class ScaffolderEntitiesProcessor implements CatalogProcessor {
diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts
index 5d4c16e2dc..71472abda6 100644
--- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts
+++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts
@@ -70,72 +70,74 @@ export const scaffolderActionsExtensionPoint =
* Catalog plugin
* @alpha
*/
-export const scaffolderPlugin = createBackendPlugin({
- id: 'scaffolder',
- register(env, options: ScaffolderPluginOptions) {
- const actionsExtensions = new ScaffolderActionsExtensionPointImpl();
- env.registerExtensionPoint(
- scaffolderActionsExtensionPoint,
- actionsExtensions,
- );
+export const scaffolderPlugin = createBackendPlugin(
+ (options: ScaffolderPluginOptions) => ({
+ id: 'scaffolder',
+ register(env) {
+ const actionsExtensions = new ScaffolderActionsExtensionPointImpl();
+ env.registerExtensionPoint(
+ scaffolderActionsExtensionPoint,
+ actionsExtensions,
+ );
- env.registerInit({
- deps: {
- logger: coreServices.logger,
- config: coreServices.config,
- reader: coreServices.urlReader,
- permissions: coreServices.permissions,
- database: coreServices.database,
- httpRouter: coreServices.httpRouter,
- catalogClient: catalogServiceRef,
- },
- async init({
- logger,
- config,
- reader,
- database,
- httpRouter,
- catalogClient,
- }) {
- const {
- additionalTemplateFilters,
- taskBroker,
- taskWorkers,
- additionalTemplateGlobals,
- } = options;
- const log = loggerToWinstonLogger(logger);
+ env.registerInit({
+ deps: {
+ logger: coreServices.logger,
+ config: coreServices.config,
+ reader: coreServices.urlReader,
+ permissions: coreServices.permissions,
+ database: coreServices.database,
+ httpRouter: coreServices.httpRouter,
+ catalogClient: catalogServiceRef,
+ },
+ async init({
+ logger,
+ config,
+ reader,
+ database,
+ httpRouter,
+ catalogClient,
+ }) {
+ const {
+ additionalTemplateFilters,
+ taskBroker,
+ taskWorkers,
+ additionalTemplateGlobals,
+ } = options;
+ const log = loggerToWinstonLogger(logger);
- const actions = options.actions || [
- ...actionsExtensions.actions,
- ...createBuiltinActions({
- integrations: ScmIntegrations.fromConfig(config),
+ const actions = options.actions || [
+ ...actionsExtensions.actions,
+ ...createBuiltinActions({
+ integrations: ScmIntegrations.fromConfig(config),
+ catalogClient,
+ reader,
+ config,
+ additionalTemplateFilters,
+ additionalTemplateGlobals,
+ }),
+ ];
+
+ const actionIds = actions.map(action => action.id).join(', ');
+ log.info(
+ `Starting scaffolder with the following actions enabled ${actionIds}`,
+ );
+
+ const router = await createRouter({
+ logger: log,
+ config,
+ database,
catalogClient,
reader,
- config,
+ actions,
+ taskBroker,
+ taskWorkers,
additionalTemplateFilters,
additionalTemplateGlobals,
- }),
- ];
-
- const actionIds = actions.map(action => action.id).join(', ');
- log.info(
- `Starting scaffolder with the following actions enabled ${actionIds}`,
- );
-
- const router = await createRouter({
- logger: log,
- config,
- database,
- catalogClient,
- reader,
- actions,
- taskBroker,
- taskWorkers,
- additionalTemplateFilters,
- additionalTemplateGlobals,
- });
- httpRouter.use(router);
- },
- });
- },
-});
+ });
+ httpRouter.use(router);
+ },
+ });
+ },
+ }),
+);
diff --git a/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.tsx b/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.tsx
index 8e48d4a561..3b47287fd6 100644
--- a/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.tsx
+++ b/plugins/scaffolder/src/components/ListTasksPage/columns/CreatedAtColumn.tsx
@@ -16,6 +16,7 @@
import { DateTime, Interval } from 'luxon';
import humanizeDuration from 'humanize-duration';
import React from 'react';
+import Typography from '@material-ui/core/Typography';
export const CreatedAtColumn = ({ createdAt }: { createdAt: string }) => {
const createdAtTime = DateTime.fromISO(createdAt);
@@ -23,5 +24,9 @@ export const CreatedAtColumn = ({ createdAt }: { createdAt: string }) => {
.toDuration()
.valueOf();
- return {humanizeDuration(formatted, { round: true })} ago
;
+ return (
+
+ {humanizeDuration(formatted, { round: true })} ago
+
+ );
};
diff --git a/plugins/scaffolder/src/components/ListTasksPage/columns/OwnerEntityColumn.tsx b/plugins/scaffolder/src/components/ListTasksPage/columns/OwnerEntityColumn.tsx
index 0c83c7cbc2..b89141392c 100644
--- a/plugins/scaffolder/src/components/ListTasksPage/columns/OwnerEntityColumn.tsx
+++ b/plugins/scaffolder/src/components/ListTasksPage/columns/OwnerEntityColumn.tsx
@@ -20,6 +20,7 @@ import useAsync from 'react-use/lib/useAsync';
import { catalogApiRef, EntityRefLink } from '@backstage/plugin-catalog-react';
import { parseEntityRef, UserEntity } from '@backstage/catalog-model';
+import Typography from '@material-ui/core/Typography';
export const OwnerEntityColumn = ({ entityRef }: { entityRef?: string }) => {
const catalogApi = useApi(catalogApiRef);
@@ -30,7 +31,7 @@ export const OwnerEntityColumn = ({ entityRef }: { entityRef?: string }) => {
);
if (!entityRef) {
- return Unknown
;
+ return Unknown ;
}
if (loading || error) {
diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx
index 3ef9291585..d24764f5d4 100644
--- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx
+++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx
@@ -153,4 +153,19 @@ describe('SearchModal', () => {
expect(getByTestId('search-bar-next')).toBeInTheDocument();
expect(getByTestId('search-bar-next')).not.toBeVisible();
});
+
+ it('should focus on its search bar when opened', async () => {
+ await renderInTestApp(
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/search': rootRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByLabelText('Search')).toHaveFocus();
+ });
});
diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx
index 956f62a01e..b1a0b2cd45 100644
--- a/plugins/search/src/components/SearchModal/SearchModal.tsx
+++ b/plugins/search/src/components/SearchModal/SearchModal.tsx
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import React from 'react';
+import React, { KeyboardEvent, useRef, useEffect, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
Dialog,
DialogActions,
@@ -94,27 +95,43 @@ const useStyles = makeStyles(theme => ({
}));
export const Modal = ({ toggleModal }: SearchModalProps) => {
- const getSearchLink = useRouteRef(rootRouteRef);
const classes = useStyles();
+ const navigate = useNavigate();
+ const { transitions } = useTheme();
+ const { focusContent } = useContent();
const { term } = useSearch();
- const { focusContent } = useContent();
- const { transitions } = useTheme();
+ const searchBarRef = useRef(null);
+ const searchPagePath = `${useRouteRef(rootRouteRef)()}?query=${term}`;
- const handleResultClick = () => {
+ useEffect(() => {
+ searchBarRef?.current?.focus();
+ });
+
+ const handleSearchResultClick = useCallback(() => {
toggleModal();
setTimeout(focusContent, transitions.duration.leavingScreen);
- };
+ }, [toggleModal, focusContent, transitions]);
- const handleKeyPress = () => {
- handleResultClick();
- };
+ const handleSearchBarKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ navigate(searchPagePath);
+ handleSearchResultClick();
+ }
+ },
+ [navigate, handleSearchResultClick, searchPagePath],
+ );
return (
<>
-
+
@@ -125,13 +142,7 @@ export const Modal = ({ toggleModal }: SearchModalProps) => {
alignItems="center"
>
- {
- toggleModal();
- setTimeout(focusContent, transitions.duration.leavingScreen);
- }}
- to={`${getSearchLink()}?query=${term}`}
- >
+
View Full Results
@@ -148,8 +159,8 @@ export const Modal = ({ toggleModal }: SearchModalProps) => {
role="button"
tabIndex={0}
key={`${document.location}-btn`}
- onClick={handleResultClick}
- onKeyPress={handleKeyPress}
+ onClick={handleSearchResultClick}
+ onKeyDown={handleSearchResultClick}
>
{
}, []);
if (loading) {
- return loading...
;
+ return loading... ;
}
if (error || !value || !value.length) {
- return could not load questions
;
+ return could not load questions ;
}
const getSecondaryText = (answer_count: Number) =>
diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md
index 8dc8efbb24..a757f0d67c 100644
--- a/plugins/tech-insights-backend/api-report.md
+++ b/plugins/tech-insights-backend/api-report.md
@@ -77,11 +77,22 @@ export interface FactRetrieverRegistry {
register(registration: FactRetrieverRegistration): Promise;
}
+// @public
+export const initializePersistenceContext: (
+ database: PluginDatabaseManager,
+ options?: PersistenceContextOptions,
+) => Promise;
+
// @public
export type PersistenceContext = {
techInsightsStore: TechInsightsStore;
};
+// @public
+export type PersistenceContextOptions = {
+ logger: Logger;
+};
+
// @public
export interface RouterOptions<
CheckType extends TechInsightCheck,
@@ -122,6 +133,7 @@ export interface TechInsightsOptions<
factRetrievers?: FactRetrieverRegistration[];
// (undocumented)
logger: Logger;
+ persistenceContext?: PersistenceContext;
// (undocumented)
scheduler: PluginTaskScheduler;
// (undocumented)
diff --git a/plugins/tech-insights-backend/src/index.ts b/plugins/tech-insights-backend/src/index.ts
index 28a922cd67..e9e037cce1 100644
--- a/plugins/tech-insights-backend/src/index.ts
+++ b/plugins/tech-insights-backend/src/index.ts
@@ -18,12 +18,16 @@ export * from './service/router';
export type { RouterOptions } from './service/router';
export { buildTechInsightsContext } from './service/techInsightsContextBuilder';
+export { initializePersistenceContext } from './service/persistence/persistenceContext';
export type {
TechInsightsOptions,
TechInsightsContext,
} from './service/techInsightsContextBuilder';
export type { FactRetrieverEngine } from './service/fact/FactRetrieverEngine';
-export type { PersistenceContext } from './service/persistence/persistenceContext';
+export type {
+ PersistenceContext,
+ PersistenceContextOptions,
+} from './service/persistence/persistenceContext';
export { createFactRetrieverRegistration } from './service/fact/createFactRetriever';
export type { FactRetrieverRegistry } from './service/fact/FactRetrieverRegistry';
export type { FactRetrieverRegistrationOptions } from './service/fact/createFactRetriever';
diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts
index c6751e864a..0b14f80843 100644
--- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts
+++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts
@@ -46,6 +46,11 @@ type RawDbFactSchemaRow = {
entityFilter?: string;
};
+/**
+ * Default TechInsightsDatabase implementation.
+ *
+ * @internal
+ */
export class TechInsightsDatabase implements TechInsightsStore {
private readonly CHUNK_SIZE = 50;
diff --git a/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts b/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts
index 575ee67bdf..07badb038c 100644
--- a/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts
+++ b/plugins/tech-insights-backend/src/service/persistence/persistenceContext.ts
@@ -36,22 +36,27 @@ export type PersistenceContext = {
techInsightsStore: TechInsightsStore;
};
-export type CreateDatabaseOptions = {
+/**
+ * A Container for persistence context initialization options
+ *
+ * @public
+ */
+export type PersistenceContextOptions = {
logger: Logger;
};
-const defaultOptions: CreateDatabaseOptions = {
+const defaultOptions: PersistenceContextOptions = {
logger: getVoidLogger(),
};
/**
- * A factory method to construct persistence context for running implementation.
+ * A factory function to construct persistence context for running implementation.
*
* @public
*/
export const initializePersistenceContext = async (
database: PluginDatabaseManager,
- options: CreateDatabaseOptions = defaultOptions,
+ options: PersistenceContextOptions = defaultOptions,
): Promise => {
const client = await database.getClient();
diff --git a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts
index f85d99b2a8..20693d0a7d 100644
--- a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts
+++ b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts
@@ -74,6 +74,12 @@ export interface TechInsightsOptions<
*/
factRetrieverRegistry?: FactRetrieverRegistry;
+ /**
+ * Optional persistenceContext implementation that replaces the default one.
+ * This can be used to replace underlying database with a more suitable implementation if needed
+ */
+ persistenceContext?: PersistenceContext;
+
logger: Logger;
config: Config;
discovery: PluginEndpointDiscovery;
@@ -139,9 +145,11 @@ export const buildTechInsightsContext = async <
const factRetrieverRegistry = buildFactRetrieverRegistry();
- const persistenceContext = await initializePersistenceContext(database, {
- logger,
- });
+ const persistenceContext =
+ options.persistenceContext ??
+ (await initializePersistenceContext(database, {
+ logger,
+ }));
const factRetrieverEngine = await DefaultFactRetrieverEngine.create({
scheduler,
diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx
index 9ad8126ac4..09badecdcd 100644
--- a/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx
+++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { ClassNameMap } from '@material-ui/core/styles/withStyles';
+import Typography from '@material-ui/core/Typography/Typography';
import React from 'react';
import { Entry, Ring } from '../../utils/types';
import { RadarLegendLink } from './RadarLegendLink';
@@ -38,7 +39,9 @@ export const RadarLegendRing = ({
{ring.name}
{entries.length === 0 ? (
-
(empty)
+
+ (empty)
+
) : (
{entries.map(entry => (
diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx
index 49f856eddd..802acef2a8 100644
--- a/plugins/tech-radar/src/components/RadarPage.tsx
+++ b/plugins/tech-radar/src/components/RadarPage.tsx
@@ -23,6 +23,7 @@ import {
Link,
} from '@backstage/core-components';
import { Grid, Input, makeStyles } from '@material-ui/core';
+import Typography from '@material-ui/core/Typography';
import React from 'react';
import { RadarComponent, TechRadarComponentProps } from './RadarComponent';
@@ -79,7 +80,7 @@ export function RadarPage(props: TechRadarPageProps) {
onChange={e => setSearchText(e.target.value)}
/>
-
+
This is used for visualizing the official guidelines of different
areas of software development such as languages, frameworks,
infrastructure and processes. You can find an explanation for the
@@ -88,7 +89,7 @@ export function RadarPage(props: TechRadarPageProps) {
Zalando Tech Radar
.
-
+
diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json
index 2b1366b2ed..cf611dd41b 100644
--- a/plugins/user-settings/package.json
+++ b/plugins/user-settings/package.json
@@ -36,6 +36,7 @@
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
+ "@backstage/plugin-catalog-react": "workspace:^",
"@backstage/theme": "workspace:^",
"@backstage/types": "workspace:^",
"@material-ui/core": "^4.12.2",
diff --git a/plugins/user-settings/src/components/General/UserSettingsIdentityCard.test.tsx b/plugins/user-settings/src/components/General/UserSettingsIdentityCard.test.tsx
index 19c3eb2182..026e0517bb 100644
--- a/plugins/user-settings/src/components/General/UserSettingsIdentityCard.test.tsx
+++ b/plugins/user-settings/src/components/General/UserSettingsIdentityCard.test.tsx
@@ -24,6 +24,7 @@ import React from 'react';
import { UserSettingsIdentityCard } from './UserSettingsIdentityCard';
import { ApiProvider } from '@backstage/core-app-api';
import { identityApiRef } from '@backstage/core-plugin-api';
+import { entityRouteRef } from '@backstage/plugin-catalog-react';
const apiRegistry = TestApiRegistry.from([
identityApiRef,
@@ -32,7 +33,7 @@ const apiRegistry = TestApiRegistry.from([
getBackstageIdentity: jest.fn(async () => ({
type: 'user' as const,
userEntityRef: 'foo:bar/foobar',
- ownershipEntityRefs: ['test-ownership'],
+ ownershipEntityRefs: ['user:default/test-ownership'],
})),
},
]);
@@ -44,10 +45,13 @@ describe(' ', () => {
,
+ {
+ mountedRoutes: { '/catalog/:namespace/:kind/:name': entityRouteRef },
+ },
),
);
- expect(screen.getByText('test-ownership')).toBeInTheDocument();
+ expect(screen.getByText('user:default/test-ownership')).toBeInTheDocument();
expect(screen.getByText('foo:bar/foobar')).toBeInTheDocument();
});
});
diff --git a/plugins/user-settings/src/components/General/UserSettingsIdentityCard.tsx b/plugins/user-settings/src/components/General/UserSettingsIdentityCard.tsx
index 1369f4aab8..a398877ce9 100644
--- a/plugins/user-settings/src/components/General/UserSettingsIdentityCard.tsx
+++ b/plugins/user-settings/src/components/General/UserSettingsIdentityCard.tsx
@@ -15,40 +15,46 @@
*/
import { InfoCard } from '@backstage/core-components';
-import React from 'react';
-import { useUserProfile } from '../useUserProfileInfo';
-import Chip from '@material-ui/core/Chip';
+import { EntityRefLinks } from '@backstage/plugin-catalog-react';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
+import React from 'react';
+import { useUserProfile } from '../useUserProfileInfo';
-/** @public */
-export const UserSettingsIdentityCard = () => {
+const Contents = () => {
const { backstageIdentity } = useUserProfile();
+ if (!backstageIdentity) {
+ return No Backstage Identity ;
+ }
+
return (
-
-
-
-
-
-
- User Entity:{' '}
-
-
-
- Ownership Entities:{' '}
- {backstageIdentity?.ownershipEntityRefs.map(it => (
-
- ))}
-
-
-
-
+
+
+
+ User Entity:{' '}
+ ref}
+ />
+
-
+
+
+ Ownership Entities:{' '}
+ ref}
+ />
+
+
+
);
};
+
+/** @public */
+export const UserSettingsIdentityCard = () => (
+
+
+
+);
diff --git a/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.tsx b/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.tsx
index f00ee03d56..54d84ff6c6 100644
--- a/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.tsx
+++ b/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.tsx
@@ -15,6 +15,7 @@
*/
import { createStyles, makeStyles, useTheme } from '@material-ui/core';
+import Typography from '@material-ui/core/Typography';
import React from 'react';
import {
Bar,
@@ -91,7 +92,7 @@ export const BuildTimeline = ({
width,
}: BuildTimelineProps) => {
const theme = useTheme();
- if (!targets.length) return No Targets
;
+ if (!targets.length) return No Targets ;
const data = getTimelineData(targets);
diff --git a/storybook/yarn.lock b/storybook/yarn.lock
index 2a7fd49da3..94167a34a7 100644
--- a/storybook/yarn.lock
+++ b/storybook/yarn.lock
@@ -2966,90 +2966,90 @@ __metadata:
languageName: node
linkType: hard
-"@swc/core-darwin-arm64@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-darwin-arm64@npm:1.3.25"
+"@swc/core-darwin-arm64@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-darwin-arm64@npm:1.3.26"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-darwin-x64@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-darwin-x64@npm:1.3.25"
+"@swc/core-darwin-x64@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-darwin-x64@npm:1.3.26"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@swc/core-linux-arm-gnueabihf@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.25"
+"@swc/core-linux-arm-gnueabihf@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.26"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@swc/core-linux-arm64-gnu@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-arm64-gnu@npm:1.3.25"
+"@swc/core-linux-arm64-gnu@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-arm64-gnu@npm:1.3.26"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-arm64-musl@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-arm64-musl@npm:1.3.25"
+"@swc/core-linux-arm64-musl@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-arm64-musl@npm:1.3.26"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-linux-x64-gnu@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-x64-gnu@npm:1.3.25"
+"@swc/core-linux-x64-gnu@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-x64-gnu@npm:1.3.26"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-x64-musl@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-x64-musl@npm:1.3.25"
+"@swc/core-linux-x64-musl@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-x64-musl@npm:1.3.26"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-win32-arm64-msvc@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-win32-arm64-msvc@npm:1.3.25"
+"@swc/core-win32-arm64-msvc@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-win32-arm64-msvc@npm:1.3.26"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-win32-ia32-msvc@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-win32-ia32-msvc@npm:1.3.25"
+"@swc/core-win32-ia32-msvc@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-win32-ia32-msvc@npm:1.3.26"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@swc/core-win32-x64-msvc@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-win32-x64-msvc@npm:1.3.25"
+"@swc/core-win32-x64-msvc@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-win32-x64-msvc@npm:1.3.26"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.9":
- version: 1.3.25
- resolution: "@swc/core@npm:1.3.25"
+ version: 1.3.26
+ resolution: "@swc/core@npm:1.3.26"
dependencies:
- "@swc/core-darwin-arm64": 1.3.25
- "@swc/core-darwin-x64": 1.3.25
- "@swc/core-linux-arm-gnueabihf": 1.3.25
- "@swc/core-linux-arm64-gnu": 1.3.25
- "@swc/core-linux-arm64-musl": 1.3.25
- "@swc/core-linux-x64-gnu": 1.3.25
- "@swc/core-linux-x64-musl": 1.3.25
- "@swc/core-win32-arm64-msvc": 1.3.25
- "@swc/core-win32-ia32-msvc": 1.3.25
- "@swc/core-win32-x64-msvc": 1.3.25
+ "@swc/core-darwin-arm64": 1.3.26
+ "@swc/core-darwin-x64": 1.3.26
+ "@swc/core-linux-arm-gnueabihf": 1.3.26
+ "@swc/core-linux-arm64-gnu": 1.3.26
+ "@swc/core-linux-arm64-musl": 1.3.26
+ "@swc/core-linux-x64-gnu": 1.3.26
+ "@swc/core-linux-x64-musl": 1.3.26
+ "@swc/core-win32-arm64-msvc": 1.3.26
+ "@swc/core-win32-ia32-msvc": 1.3.26
+ "@swc/core-win32-x64-msvc": 1.3.26
dependenciesMeta:
"@swc/core-darwin-arm64":
optional: true
@@ -3071,7 +3071,7 @@ __metadata:
optional: true
"@swc/core-win32-x64-msvc":
optional: true
- checksum: de45a7dd871cc9497ad998d6a320d3c95cb9c74fdcb70590ff1f631e75001820d021bbfd5c463e9172afcb5ee47bffaa8fb893230e1329538c9f7afbd5ed45cf
+ checksum: 656a2197bd8bd50b112077e8c7f9156527ec66cb06fed2cefb9e2610e3cde13e8416783f84edda67b9af4e27d6b169bc1a447d7c6c73cf64a1b3ec428b0737ab
languageName: node
linkType: hard
diff --git a/yarn.lock b/yarn.lock
index 14835db18d..282b7b24bc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3400,6 +3400,7 @@ __metadata:
fs-extra: 10.1.0
helmet: ^6.0.0
http-errors: ^2.0.0
+ lodash: ^4.17.21
minimatch: ^5.0.0
morgan: ^1.10.0
node-forge: ^1.3.1
@@ -3558,11 +3559,16 @@ __metadata:
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
+ "@backstage/types": "workspace:^"
+ "@types/supertest": ^2.0.8
better-sqlite3: ^8.0.0
+ express: ^4.17.1
+ express-promise-router: ^4.1.0
knex: ^2.0.0
msw: ^0.49.0
mysql2: ^2.2.5
pg: ^8.3.0
+ supertest: ^6.1.3
testcontainers: ^8.1.2
uuid: ^8.0.0
languageName: unknown
@@ -4513,7 +4519,6 @@ __metadata:
express: ^4.17.1
express-promise-router: ^4.1.0
fs-extra: 10.1.0
- get-port: ^6.1.2
globby: ^11.0.0
helmet: ^6.0.0
knex: ^2.0.0
@@ -5112,7 +5117,6 @@ __metadata:
"@types/luxon": ^3.0.0
express: ^4.17.1
express-promise-router: ^4.1.0
- get-port: ^6.1.2
knex: ^2.0.0
lodash: ^4.17.21
luxon: ^3.0.0
@@ -8447,6 +8451,7 @@ __metadata:
"@backstage/core-plugin-api": "workspace:^"
"@backstage/dev-utils": "workspace:^"
"@backstage/errors": "workspace:^"
+ "@backstage/plugin-catalog-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
"@backstage/types": "workspace:^"
@@ -13523,90 +13528,90 @@ __metadata:
languageName: node
linkType: hard
-"@swc/core-darwin-arm64@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-darwin-arm64@npm:1.3.25"
+"@swc/core-darwin-arm64@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-darwin-arm64@npm:1.3.26"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-darwin-x64@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-darwin-x64@npm:1.3.25"
+"@swc/core-darwin-x64@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-darwin-x64@npm:1.3.26"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@swc/core-linux-arm-gnueabihf@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.25"
+"@swc/core-linux-arm-gnueabihf@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.26"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@swc/core-linux-arm64-gnu@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-arm64-gnu@npm:1.3.25"
+"@swc/core-linux-arm64-gnu@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-arm64-gnu@npm:1.3.26"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-arm64-musl@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-arm64-musl@npm:1.3.25"
+"@swc/core-linux-arm64-musl@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-arm64-musl@npm:1.3.26"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-linux-x64-gnu@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-x64-gnu@npm:1.3.25"
+"@swc/core-linux-x64-gnu@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-x64-gnu@npm:1.3.26"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-x64-musl@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-linux-x64-musl@npm:1.3.25"
+"@swc/core-linux-x64-musl@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-linux-x64-musl@npm:1.3.26"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-win32-arm64-msvc@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-win32-arm64-msvc@npm:1.3.25"
+"@swc/core-win32-arm64-msvc@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-win32-arm64-msvc@npm:1.3.26"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-win32-ia32-msvc@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-win32-ia32-msvc@npm:1.3.25"
+"@swc/core-win32-ia32-msvc@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-win32-ia32-msvc@npm:1.3.26"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@swc/core-win32-x64-msvc@npm:1.3.25":
- version: 1.3.25
- resolution: "@swc/core-win32-x64-msvc@npm:1.3.25"
+"@swc/core-win32-x64-msvc@npm:1.3.26":
+ version: 1.3.26
+ resolution: "@swc/core-win32-x64-msvc@npm:1.3.26"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.9":
- version: 1.3.25
- resolution: "@swc/core@npm:1.3.25"
+ version: 1.3.26
+ resolution: "@swc/core@npm:1.3.26"
dependencies:
- "@swc/core-darwin-arm64": 1.3.25
- "@swc/core-darwin-x64": 1.3.25
- "@swc/core-linux-arm-gnueabihf": 1.3.25
- "@swc/core-linux-arm64-gnu": 1.3.25
- "@swc/core-linux-arm64-musl": 1.3.25
- "@swc/core-linux-x64-gnu": 1.3.25
- "@swc/core-linux-x64-musl": 1.3.25
- "@swc/core-win32-arm64-msvc": 1.3.25
- "@swc/core-win32-ia32-msvc": 1.3.25
- "@swc/core-win32-x64-msvc": 1.3.25
+ "@swc/core-darwin-arm64": 1.3.26
+ "@swc/core-darwin-x64": 1.3.26
+ "@swc/core-linux-arm-gnueabihf": 1.3.26
+ "@swc/core-linux-arm64-gnu": 1.3.26
+ "@swc/core-linux-arm64-musl": 1.3.26
+ "@swc/core-linux-x64-gnu": 1.3.26
+ "@swc/core-linux-x64-musl": 1.3.26
+ "@swc/core-win32-arm64-msvc": 1.3.26
+ "@swc/core-win32-ia32-msvc": 1.3.26
+ "@swc/core-win32-x64-msvc": 1.3.26
dependenciesMeta:
"@swc/core-darwin-arm64":
optional: true
@@ -13628,7 +13633,7 @@ __metadata:
optional: true
"@swc/core-win32-x64-msvc":
optional: true
- checksum: de45a7dd871cc9497ad998d6a320d3c95cb9c74fdcb70590ff1f631e75001820d021bbfd5c463e9172afcb5ee47bffaa8fb893230e1329538c9f7afbd5ed45cf
+ checksum: 656a2197bd8bd50b112077e8c7f9156527ec66cb06fed2cefb9e2610e3cde13e8416783f84edda67b9af4e27d6b169bc1a447d7c6c73cf64a1b3ec428b0737ab
languageName: node
linkType: hard
@@ -23809,13 +23814,6 @@ __metadata:
languageName: node
linkType: hard
-"get-port@npm:^6.1.2":
- version: 6.1.2
- resolution: "get-port@npm:6.1.2"
- checksum: e3c3d591492a11393455ef220f24c812a28f7da56ec3e4a2512d931a1f196d42850b50ac6138349a44622eda6dc3c0ccd8495cd91376d968e2d9e6f6f849e0a9
- languageName: node
- linkType: hard
-
"get-stdin@npm:^8.0.0":
version: 8.0.0
resolution: "get-stdin@npm:8.0.0"