diff --git a/.changeset/chatty-seals-tap.md b/.changeset/chatty-seals-tap.md
new file mode 100644
index 0000000000..01d69e1562
--- /dev/null
+++ b/.changeset/chatty-seals-tap.md
@@ -0,0 +1,6 @@
+---
+'@backstage/create-app': patch
+'@backstage/plugin-catalog': patch
+---
+
+Add link from Template entity to the scaffolder launch page for the template in the AboutCard.
diff --git a/.changeset/clean-meals-pay.md b/.changeset/clean-meals-pay.md
new file mode 100644
index 0000000000..629549112c
--- /dev/null
+++ b/.changeset/clean-meals-pay.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-jenkins-backend': patch
+---
+
+Don't expose username and authentication header if configured.
diff --git a/.changeset/clean-years-whisper.md b/.changeset/clean-years-whisper.md
new file mode 100644
index 0000000000..fd77430cd7
--- /dev/null
+++ b/.changeset/clean-years-whisper.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-home': patch
+---
+
+Update set up documentation to export homepage as element instead of React component.
diff --git a/.changeset/giant-maps-compete.md b/.changeset/giant-maps-compete.md
new file mode 100644
index 0000000000..776332b308
--- /dev/null
+++ b/.changeset/giant-maps-compete.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-pagerduty': patch
+---
+
+Add a homepage widget for the `Pagerduty` component.
diff --git a/.changeset/nice-readers-heal.md b/.changeset/nice-readers-heal.md
new file mode 100644
index 0000000000..54534f8289
--- /dev/null
+++ b/.changeset/nice-readers-heal.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-common': patch
+---
+
+Fixed typo in HostDiscovery's JSDoc
diff --git a/.changeset/selfish-carrots-sneeze.md b/.changeset/selfish-carrots-sneeze.md
new file mode 100644
index 0000000000..0920f78f86
--- /dev/null
+++ b/.changeset/selfish-carrots-sneeze.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-proxy-backend': patch
+---
+
+Marked headers `Authorization` and `X-Api-Key` as secret in order to not show up in frontend configuration.
diff --git a/.changeset/weak-rats-serve.md b/.changeset/weak-rats-serve.md
new file mode 100644
index 0000000000..fe7cd78510
--- /dev/null
+++ b/.changeset/weak-rats-serve.md
@@ -0,0 +1,9 @@
+---
+'@backstage/core-app-api': patch
+'@backstage/core-plugin-api': patch
+'@backstage/dev-utils': patch
+'@backstage/plugin-scaffolder-react': patch
+'@backstage/plugin-scaffolder': patch
+---
+
+Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit.
diff --git a/docs/getting-started/homepage.md b/docs/getting-started/homepage.md
index 63a97ec54f..9c6f6380a1 100644
--- a/docs/getting-started/homepage.md
+++ b/docs/getting-started/homepage.md
@@ -40,10 +40,10 @@ Inside your `packages/app` directory, create a new file where our new homepage c
```tsx
import React from 'react';
-export const HomePage = () => {
+export const homePage = (
/* We will shortly compose a pretty homepage here. */
- return
Welcome to Backstage!
;
-};
+
Welcome to Backstage!
+);
```
#### 3. Update router for the root `/` route
@@ -161,13 +161,11 @@ import React from 'react';
import Grid from '@material-ui/core/Grid';
import { HomePageCompanyLogo } from '@backstage/plugin-home';
-export const HomePage = () => {
- return (
-
-
-
-
+export const homePage = (
+
+
+
- );
-};
+
+);
```
diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md
index 39532e806f..39b60c76c5 100644
--- a/docs/integrations/github/org.md
+++ b/docs/integrations/github/org.md
@@ -261,8 +261,8 @@ returned identity.
async (user, ctx): Promise => {
const entity = await defaultUserTransformer(user, ctx);
- if (entity && user.organizationVerifiedDomainEmails) {
- entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0] || '';
+ if (entity && user.organizationVerifiedDomainEmails?.length) {
+ entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0];
}
return entity;
diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml
index d58ca322eb..54dea6454a 100644
--- a/microsite/data/plugins/analytics-module-ga.yaml
+++ b/microsite/data/plugins/analytics-module-ga.yaml
@@ -2,7 +2,7 @@
title: 'Analytics Module: Google Analytics'
author: Spotify
authorUrl: https://github.com/spotify
-category: Analytics
+category: Monitoring
description: Track usage of your Backstage instance using Google Analytics.
documentation: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga/README.md
iconUrl: /img/ga-icon.png
diff --git a/microsite/data/plugins/firehydrant.yaml b/microsite/data/plugins/firehydrant.yaml
index b52357e808..8a1f122051 100644
--- a/microsite/data/plugins/firehydrant.yaml
+++ b/microsite/data/plugins/firehydrant.yaml
@@ -2,7 +2,7 @@
title: FireHydrant
author: FireHydrant
authorUrl: https://firehydrant.io/
-category: Incident Management
+category: Monitoring
description: View service incidents information from FireHydrant, such as active incidents and incident metrics, directly within Backstage.
documentation: https://github.com/backstage/backstage/blob/master/plugins/firehydrant/README.md
iconUrl: https://raw.githubusercontent.com/backstage/backstage/master/plugins/firehydrant/doc/firehydrant_logo.png
diff --git a/microsite/data/plugins/git-release-manager.yaml b/microsite/data/plugins/git-release-manager.yaml
index 6d357a23f6..947209e6d5 100644
--- a/microsite/data/plugins/git-release-manager.yaml
+++ b/microsite/data/plugins/git-release-manager.yaml
@@ -2,7 +2,7 @@
title: GitHub Release Manager
author: Spotify
authorUrl: https://github.com/spotify
-category: Release management
+category: Source Control Mgmt
description: Manage releases without having to juggle git commands.
documentation: https://github.com/backstage/backstage/tree/master/plugins/git-release-manager
iconUrl: /img/git-release-manager-logo.svg
diff --git a/microsite/data/plugins/keycloak.yaml b/microsite/data/plugins/keycloak.yaml
index 882d43f515..ea657c74f2 100644
--- a/microsite/data/plugins/keycloak.yaml
+++ b/microsite/data/plugins/keycloak.yaml
@@ -2,7 +2,7 @@
title: Authentication and Authorization with Keycloak
author: Red Hat
authorUrl: https://redhat.com
-category: Authentication
+category: Authentication/Authorization
description: Load users and groups from Keycloak, enabling use of multiple authentication providers to be applied to Backstage entities.
documentation: https://janus-idp.io/plugins/keycloak
iconUrl: http://janus-idp.io/images/plugins/keycloak.svg
diff --git a/microsite/data/plugins/kpt-config-as-data.yaml b/microsite/data/plugins/kpt-config-as-data.yaml
index 2ed67262a8..af157a70c3 100644
--- a/microsite/data/plugins/kpt-config-as-data.yaml
+++ b/microsite/data/plugins/kpt-config-as-data.yaml
@@ -2,7 +2,7 @@
title: Configuration as Data
author: kpt
authorUrl: https://kpt.dev
-category: Configuration
+category: Configuration Management
description: Configuration GUI over GitOps using kpt, with WYSIWYG editing, review and approval, versioning and undo, and package cloning and upgrades.
documentation: https://github.com/GoogleContainerTools/kpt-backstage-plugins/tree/main/plugins/cad
iconUrl: https://github.com/GoogleContainerTools/kpt/blob/main/logo/KptLogoSmall.png?raw=true
diff --git a/microsite/data/plugins/ldap-auth.yml b/microsite/data/plugins/ldap-auth.yml
index 1800f7370c..7446f28f6f 100644
--- a/microsite/data/plugins/ldap-auth.yml
+++ b/microsite/data/plugins/ldap-auth.yml
@@ -2,7 +2,7 @@
title: LDAP Auth
author: ImmobiliareLabs
authorUrl: https://github.com/immobiliare
-category: Authentication
+category: Authentication/Authorization
description: Authenticate users to an external LDAP server
documentation: https://github.com/immobiliare/backstage-plugin-ldap-auth/blob/main/README.md
iconUrl: https://avatars.githubusercontent.com/u/10090828
diff --git a/microsite/data/plugins/ocm.yaml b/microsite/data/plugins/ocm.yaml
index 15c23a0fb7..9e9b8ff1b7 100644
--- a/microsite/data/plugins/ocm.yaml
+++ b/microsite/data/plugins/ocm.yaml
@@ -2,7 +2,7 @@
title: Multi Cluster View with OCM
author: Red Hat
authorUrl: https://redhat.com
-category: OCM
+category: Infrastructure
description: View clusters from OCM's MultiClusterHub and MultiCluster Engine in Backstage.
documentation: https://janus-idp.io/plugins/ocm
iconUrl: http://janus-idp.io/images/plugins/ocm.svg
diff --git a/microsite/data/plugins/okta-entity-providers.yaml b/microsite/data/plugins/okta-entity-providers.yaml
index 047992e785..4544c9eae7 100644
--- a/microsite/data/plugins/okta-entity-providers.yaml
+++ b/microsite/data/plugins/okta-entity-providers.yaml
@@ -2,7 +2,7 @@
title: Okta Organization Entity Provider
author: roadie.io
authorUrl: https://roadie.io/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=okta-entity-provider
-category: Identity
+category: Authentication/Authorization
description: Load users and groups from Okta into the Backstage catalog.
documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-okta
iconUrl: https://roadie.io/images/logos/okta.png
diff --git a/microsite/data/plugins/rootly.yaml b/microsite/data/plugins/rootly.yaml
index 4aaf10ee7b..fa65e2d6ef 100644
--- a/microsite/data/plugins/rootly.yaml
+++ b/microsite/data/plugins/rootly.yaml
@@ -2,7 +2,7 @@
title: Rootly
author: Rootly
authorUrl: https://rootly.com/
-category: Incident Management
+category: Monitoring
description: Import your Backstage entities into Rootly services and view incidents & analytics.
documentation: https://github.com/rootlyhq/backstage-plugin/blob/master/README.md
iconUrl: https://raw.githubusercontent.com/rootlyhq/backstage-plugin/master/docs/logo.png
diff --git a/microsite/data/plugins/s3-viewer.yaml b/microsite/data/plugins/s3-viewer.yaml
index 2eeeb23415..57d5b22ba1 100644
--- a/microsite/data/plugins/s3-viewer.yaml
+++ b/microsite/data/plugins/s3-viewer.yaml
@@ -2,7 +2,7 @@
title: S3 Viewer
author: Spreadgroup
authorUrl: https://github.com/spreadshirt
-category: AWS
+category: Infrastructure
description: Visualization of S3 buckets and their contents in file explorer style.
documentation: https://github.com/spreadshirt/backstage-plugin-s3/
iconUrl: /img/s3-bucket.svg
diff --git a/microsite/data/plugins/solo-io-dev-portal.yaml b/microsite/data/plugins/solo-io-dev-portal.yaml
new file mode 100644
index 0000000000..dcc5722092
--- /dev/null
+++ b/microsite/data/plugins/solo-io-dev-portal.yaml
@@ -0,0 +1,10 @@
+---
+title: Solo.io Gloo Platform Portal
+author: Solo.io
+authorUrl: https://solo.io
+category: Infrastructure
+description: View, test, and manage your Gloo Platform Portal APIs through our Backstage plugin.
+documentation: https://github.com/solo-io/dev-portal-backstage-public#readme
+iconUrl: img/solo-io-gloo-outline.svg
+npmPackageName: '@solo.io/dev-portal-backstage-plugin'
+addedDate: '2023-05-19'
diff --git a/microsite/data/plugins/statusneo-github-plugin.yaml b/microsite/data/plugins/statusneo-github-plugin.yaml
index 9c56dd4ae4..2497a99c1b 100644
--- a/microsite/data/plugins/statusneo-github-plugin.yaml
+++ b/microsite/data/plugins/statusneo-github-plugin.yaml
@@ -1,7 +1,7 @@
title: Statusneo GitHub Plugin
author: Statusneo
authorUrl: https://statusneo.com/
-category: GitHub
+category: Source Control Mgmt
description: Provides status cards GitHub pull requests and GitHub actions.
documentation: https://github.com/StatusNeo/backstage-plugin-github
iconUrl: /img/github-statusneo.png
diff --git a/microsite/data/plugins/tekton-pipelines.yaml b/microsite/data/plugins/tekton-pipelines.yaml
index 2da80d34bf..fe19ab2af2 100644
--- a/microsite/data/plugins/tekton-pipelines.yaml
+++ b/microsite/data/plugins/tekton-pipelines.yaml
@@ -2,7 +2,7 @@
title: Tekton Pipelines
author: JQuad Group
authorUrl: https://github.com/jquad-group
-category: CI
+category: CI/CD
description: View the status of the Tekton PipelineRun resources.
documentation: https://github.com/jquad-group/backstage-jquad#readme
iconUrl: https://raw.githubusercontent.com/jquad-group/backstage-jquad/main/img/tekton-horizontal-color.png
diff --git a/microsite/data/plugins/tekton.yaml b/microsite/data/plugins/tekton.yaml
index b1ebfb1349..fa94b89c35 100644
--- a/microsite/data/plugins/tekton.yaml
+++ b/microsite/data/plugins/tekton.yaml
@@ -2,7 +2,7 @@
title: Pipelines with Tekton
author: Red Hat
authorUrl: https://redhat.com
-category: CI
+category: CI/CD
description: Easily view Tekton PipelineRun status for your services in Backstage.
documentation: https://janus-idp.io/plugins/tekton
iconUrl: http://janus-idp.io/images/plugins/tekton.svg
diff --git a/microsite/package.json b/microsite/package.json
index 98301ce2d0..f1732c1142 100644
--- a/microsite/package.json
+++ b/microsite/package.json
@@ -20,6 +20,7 @@
"@docusaurus/module-type-aliases": "0.0.0-5580",
"@spotify/prettier-config": "^14.0.0",
"@tsconfig/docusaurus": "^1.0.6",
+ "@types/luxon": "^3.0.0",
"@types/webpack-env": "^1.18.0",
"js-yaml": "^4.1.0",
"prettier": "^2.6.2",
@@ -34,6 +35,7 @@
"@swc/core": "^1.3.46",
"clsx": "^1.1.1",
"docusaurus-plugin-sass": "^0.2.3",
+ "luxon": "^3.0.0",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
diff --git a/microsite/src/components/contentBlock/contentBlock.tsx b/microsite/src/components/contentBlock/contentBlock.tsx
index aae065db44..983f684927 100644
--- a/microsite/src/components/contentBlock/contentBlock.tsx
+++ b/microsite/src/components/contentBlock/contentBlock.tsx
@@ -1,6 +1,6 @@
import Link from '@docusaurus/Link';
import clsx from 'clsx';
-import React, { FC, PropsWithChildren, ReactNode } from 'react';
+import React, { PropsWithChildren, ReactNode } from 'react';
import contentBlockStyles from './contentBlock.module.scss';
diff --git a/microsite/src/pages/plugins/_pluginCard.tsx b/microsite/src/pages/plugins/_pluginCard.tsx
index d6b4017db5..ba8e8069d2 100644
--- a/microsite/src/pages/plugins/_pluginCard.tsx
+++ b/microsite/src/pages/plugins/_pluginCard.tsx
@@ -1,5 +1,6 @@
import Link from '@docusaurus/Link';
import { SimpleCard } from '@site/src/components/simpleCard/simpleCard';
+import clsx from 'clsx';
import React from 'react';
export interface IPluginData {
@@ -10,6 +11,8 @@ export interface IPluginData {
documentation: string;
iconUrl: string;
title: string;
+ addedDate: string;
+ isNew: boolean;
order?: number;
}
@@ -22,14 +25,17 @@ export const PluginCard = ({
description,
documentation,
iconUrl,
+ isNew = false,
title,
}: IPluginData) => (
+ {isNew &&
NEW
}
+
-
{title}
+
{title}
by {author}
diff --git a/microsite/src/pages/plugins/index.tsx b/microsite/src/pages/plugins/index.tsx
index b1c9601110..94e9df0e8d 100644
--- a/microsite/src/pages/plugins/index.tsx
+++ b/microsite/src/pages/plugins/index.tsx
@@ -1,12 +1,19 @@
import Link from '@docusaurus/Link';
+import PluginsFilter from '@site/src/components/pluginsFilter/pluginsFilter';
+import { calcIsNewPlugin } from '@site/src/util/calcIsNewPlugin';
+import { truncateDescription } from '@site/src/util/truncateDescription';
+import { ChipCategory } from '@site/src/util/types';
import Layout from '@theme/Layout';
import clsx from 'clsx';
import React, { useState } from 'react';
+
import { IPluginData, PluginCard } from './_pluginCard';
import pluginsStyles from './plugins.module.scss';
-import { ChipCategory } from '@site/src/util/types';
-import { truncateDescription } from '@site/src/util/truncateDescription';
-import PluginsFilter from '@site/src/components/pluginsFilter/pluginsFilter';
+
+interface IPluginsList {
+ corePlugins: IPluginData[];
+ otherPlugins: IPluginData[];
+}
const pluginsContext = require.context(
'../../../data/plugins',
@@ -14,17 +21,20 @@ const pluginsContext = require.context(
/\.ya?ml/,
);
-const plugins = pluginsContext.keys().reduce(
+const plugins: IPluginsList = pluginsContext.keys().reduce(
(acum, id) => {
- const pluginData: IPluginData = pluginsContext(id).default;
+ let pluginData: IPluginData = pluginsContext(id).default;
+ const category: keyof IPluginsList =
+ pluginData.category === 'Core Feature' ? 'corePlugins' : 'otherPlugins';
- acum[
- pluginData.category === 'Core Feature' ? 'corePlugins' : 'otherPlugins'
- ].push(truncateDescription(pluginData));
+ pluginData = calcIsNewPlugin(pluginData);
+ pluginData = truncateDescription(pluginData);
+
+ acum[category].push(pluginData);
return acum;
},
- { corePlugins: [] as IPluginData[], otherPlugins: [] as IPluginData[] },
+ { corePlugins: [], otherPlugins: [] } as IPluginsList,
);
plugins.corePlugins.sort((a, b) => a.order - b.order);
diff --git a/microsite/src/pages/plugins/plugins.module.scss b/microsite/src/pages/plugins/plugins.module.scss
index d5ea5de6e7..9db634c3ed 100644
--- a/microsite/src/pages/plugins/plugins.module.scss
+++ b/microsite/src/pages/plugins/plugins.module.scss
@@ -11,6 +11,7 @@
:global(.card .card__header) {
display: grid;
row-gap: 0.25rem;
+ position: relative;
column-gap: 1rem;
align-items: center;
grid-template-columns: auto 1fr;
@@ -19,6 +20,28 @@
margin: 0;
}
+ :global(.newRibbon) {
+ --paddingX: 1.2lh;
+ --rotation: 45deg;
+ --translateX: 30%;
+ --translateY: -100%;
+
+ top: 0;
+ right: 0;
+ padding: 0 var(--paddingX);
+ position: absolute;
+ text-align: center;
+
+ transform: translateY(var(--translateY)) translateX(var(--translateX))
+ rotate(var(--rotation));
+ transform-origin: bottom left;
+ background-color: var(--ifm-color-primary);
+ }
+
+ :global(.newRibbonPadding) {
+ padding-right: 1.2lh;
+ }
+
:global(img) {
width: 80px;
grid-row: 1/4;
diff --git a/microsite/src/util/calcIsNewPlugin.tsx b/microsite/src/util/calcIsNewPlugin.tsx
new file mode 100644
index 0000000000..4b23e1d47d
--- /dev/null
+++ b/microsite/src/util/calcIsNewPlugin.tsx
@@ -0,0 +1,18 @@
+import { DateTime } from 'luxon';
+
+export const newDaysLimit = 100;
+
+export const calcIsNewPlugin = <
+ T extends { addedDate: string; isNew: boolean },
+>(
+ itemData: T,
+) => {
+ const addedDate = DateTime.fromISO(itemData.addedDate);
+ const daysOld = Math.abs(addedDate.diffNow('days').days);
+ const isNew = !isNaN(daysOld) && daysOld < newDaysLimit;
+
+ return {
+ ...itemData,
+ isNew,
+ };
+};
diff --git a/microsite/static/img/solo-io-gloo-outline.svg b/microsite/static/img/solo-io-gloo-outline.svg
new file mode 100644
index 0000000000..debb47038d
--- /dev/null
+++ b/microsite/static/img/solo-io-gloo-outline.svg
@@ -0,0 +1,3 @@
+
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index 7097fb337e..1d116ad6da 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -2941,6 +2941,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/luxon@npm:^3.0.0":
+ version: 3.3.0
+ resolution: "@types/luxon@npm:3.3.0"
+ checksum: f7e3a89fc3ca404fbc3ea538653ed6860bc28f570a8c4d6d24449b89b9b553b7d6ad6cc94a9e129c5b8c9a2b97f0c365b3017f811e59c4a859a9c219a1c918e0
+ languageName: node
+ linkType: hard
+
"@types/mdast@npm:^3.0.0":
version: 3.0.10
resolution: "@types/mdast@npm:3.0.10"
@@ -3752,10 +3759,12 @@ __metadata:
"@spotify/prettier-config": ^14.0.0
"@swc/core": ^1.3.46
"@tsconfig/docusaurus": ^1.0.6
+ "@types/luxon": ^3.0.0
"@types/webpack-env": ^1.18.0
clsx: ^1.1.1
docusaurus-plugin-sass: ^0.2.3
js-yaml: ^4.1.0
+ luxon: ^3.0.0
prettier: ^2.6.2
prism-react-renderer: ^1.3.5
react: ^17.0.2
@@ -7348,6 +7357,13 @@ __metadata:
languageName: node
linkType: hard
+"luxon@npm:^3.0.0":
+ version: 3.3.0
+ resolution: "luxon@npm:3.3.0"
+ checksum: 50cf17a0dc155c3dcacbeae8c0b7e80db425e0ba97b9cbdf12a7fc142d841ff1ab1560919f033af46240ed44e2f70c49f76e3422524c7fc8bb8d81ca47c66187
+ languageName: node
+ linkType: hard
+
"make-dir@npm:^3.0.2":
version: 3.1.0
resolution: "make-dir@npm:3.1.0"
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index bc9eac8c64..d46774a757 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -142,6 +142,7 @@ const app = createApp({
bind(catalogPlugin.externalRoutes, {
createComponent: scaffolderPlugin.routes.root,
viewTechDoc: techdocsPlugin.routes.docRoot,
+ createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
});
bind(apiDocsPlugin.externalRoutes, {
registerApi: catalogImportPlugin.routes.importPage,
diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx
index da1d0d425c..aa3739728e 100644
--- a/packages/app/src/components/home/HomePage.tsx
+++ b/packages/app/src/components/home/HomePage.tsx
@@ -30,6 +30,7 @@ import { HomePageCalendar } from '@backstage/plugin-gcalendar';
import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar';
import React from 'react';
import HomeIcon from '@material-ui/icons/Home';
+import { HomePagePagerDutyCard } from '@backstage/plugin-pagerduty';
const clockConfigs: ClockConfig[] = [
{
@@ -93,6 +94,7 @@ export const homePage = (
+
diff --git a/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx b/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx
index 52fab3c55f..05d9a4eecd 100644
--- a/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx
+++ b/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx
@@ -38,7 +38,7 @@ import {
} from '@backstage/plugin-search-react';
import { stackOverflowApiRef, HomePageStackOverflowQuestions } from '@backstage/plugin-stack-overflow';
import { Grid, makeStyles } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
const entities = [
{
@@ -107,7 +107,7 @@ starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4');
export default {
title: 'Plugins/Home/Templates',
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
<>
;
+ NotFoundErrorPage: ComponentType>;
BootErrorPage: ComponentType;
- Progress: ComponentType<{}>;
- Router: ComponentType<{
- basename?: string;
- }>;
+ Progress: ComponentType>;
+ Router: ComponentType<
+ PropsWithChildren<{
+ basename?: string;
+ }>
+ >;
ErrorBoundaryFallback: ComponentType;
- ThemeProvider?: ComponentType<{}>;
+ ThemeProvider?: ComponentType>;
SignInPage?: ComponentType;
};
@@ -274,9 +276,9 @@ export type AuthApiCreateOptions = {
export type BackstageApp = {
getPlugins(): BackstagePlugin[];
getSystemIcon(key: string): IconComponent | undefined;
- createRoot(element: JSX.Element): ComponentType<{}>;
- getProvider(): ComponentType<{}>;
- getRouter(): ComponentType<{}>;
+ createRoot(element: JSX.Element): ComponentType>;
+ getProvider(): ComponentType>;
+ getRouter(): ComponentType>;
};
// @public
@@ -316,10 +318,10 @@ export type BitbucketSession = {
};
// @public
-export type BootErrorPageProps = {
+export type BootErrorPageProps = PropsWithChildren<{
step: 'load-config' | 'load-chunk';
error: Error;
-};
+}>;
export { ConfigReader };
@@ -359,11 +361,11 @@ export class ErrorApiForwarder implements ErrorApi {
}
// @public
-export type ErrorBoundaryFallbackProps = {
+export type ErrorBoundaryFallbackProps = PropsWithChildren<{
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
-};
+}>;
// @public
export const FeatureFlagged: (props: FeatureFlaggedProps) => JSX.Element;
@@ -596,9 +598,9 @@ export class SamlAuth
}
// @public
-export type SignInPageProps = {
+export type SignInPageProps = PropsWithChildren<{
onSignInSuccess(identityApi: IdentityApi): void;
-};
+}>;
// @public
export class UnhandledErrorForwarder {
diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx
index 33d43cbca6..470f7f8d75 100644
--- a/packages/core-app-api/src/app/AppManager.tsx
+++ b/packages/core-app-api/src/app/AppManager.tsx
@@ -242,7 +242,7 @@ export class AppManager implements BackstageApp {
return this.components;
}
- createRoot(element: JSX.Element): ComponentType<{}> {
+ createRoot(element: JSX.Element): ComponentType> {
const AppProvider = this.getProvider();
const AppRoot = () => {
return {element};
@@ -251,7 +251,7 @@ export class AppManager implements BackstageApp {
}
#getProviderCalled = false;
- getProvider(): ComponentType<{}> {
+ getProvider(): ComponentType> {
if (this.#getProviderCalled) {
throw new Error(
'app.getProvider() or app.createRoot() has already been called, and can only be called once',
@@ -404,7 +404,7 @@ export class AppManager implements BackstageApp {
return Provider;
}
- getRouter(): ComponentType<{}> {
+ getRouter(): ComponentType> {
return AppRouter;
}
diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts
index c34407ca3a..7f38e2d006 100644
--- a/packages/core-app-api/src/app/types.ts
+++ b/packages/core-app-api/src/app/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { ComponentType } from 'react';
+import { ComponentType, PropsWithChildren } from 'react';
import {
AnyApiFactory,
AppTheme,
@@ -33,33 +33,33 @@ import { AppConfig } from '@backstage/config';
*
* @public
*/
-export type BootErrorPageProps = {
+export type BootErrorPageProps = PropsWithChildren<{
step: 'load-config' | 'load-chunk';
error: Error;
-};
+}>;
/**
* Props for the `SignInPage` component of {@link AppComponents}.
*
* @public
*/
-export type SignInPageProps = {
+export type SignInPageProps = PropsWithChildren<{
/**
* Set the IdentityApi on successful sign-in. This should only be called once.
*/
onSignInSuccess(identityApi: IdentityApi): void;
-};
+}>;
/**
* Props for the fallback error boundary.
*
* @public
*/
-export type ErrorBoundaryFallbackProps = {
+export type ErrorBoundaryFallbackProps = PropsWithChildren<{
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
-};
+}>;
/**
* A set of replaceable core components that are part of every Backstage app.
@@ -67,12 +67,12 @@ export type ErrorBoundaryFallbackProps = {
* @public
*/
export type AppComponents = {
- NotFoundErrorPage: ComponentType<{}>;
+ NotFoundErrorPage: ComponentType>;
BootErrorPage: ComponentType;
- Progress: ComponentType<{}>;
- Router: ComponentType<{ basename?: string }>;
+ Progress: ComponentType>;
+ Router: ComponentType>;
ErrorBoundaryFallback: ComponentType;
- ThemeProvider?: ComponentType<{}>;
+ ThemeProvider?: ComponentType>;
/**
* An optional sign-in page that will be rendered instead of the AppRouter at startup.
@@ -319,7 +319,7 @@ export type BackstageApp = {
* );
* ```
*/
- createRoot(element: JSX.Element): ComponentType<{}>;
+ createRoot(element: JSX.Element): ComponentType>;
/**
* Provider component that should wrap the Router created with getRouter()
@@ -327,7 +327,7 @@ export type BackstageApp = {
*
* @deprecated Use {@link BackstageApp.createRoot} instead.
*/
- getProvider(): ComponentType<{}>;
+ getProvider(): ComponentType>;
/**
* Router component that should wrap the App Routes create with getRoutes()
@@ -335,7 +335,7 @@ export type BackstageApp = {
*
* @deprecated Import and use the {@link AppRouter} component from `@backstage/core-app-api` instead
*/
- getRouter(): ComponentType<{}>;
+ getRouter(): ComponentType>;
};
/**
diff --git a/packages/core-components/src/components/Link/Link.stories.tsx b/packages/core-components/src/components/Link/Link.stories.tsx
index 6049ec68b1..e069459e76 100644
--- a/packages/core-components/src/components/Link/Link.stories.tsx
+++ b/packages/core-components/src/components/Link/Link.stories.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { Link } from './Link';
import {
Route,
@@ -37,7 +37,7 @@ export default {
title: 'Navigation/Link',
component: Link,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
diff --git a/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx b/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx
index 3dc49abb7f..ec9398e47d 100644
--- a/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx
+++ b/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { LinkButton } from './LinkButton';
import { useLocation } from 'react-router-dom';
import { createRouteRef, useRouteRef } from '@backstage/core-plugin-api';
@@ -39,7 +39,7 @@ export default {
title: 'Inputs/Button',
component: LinkButton,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
<>
diff --git a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx
index d244ef7715..fca963eb74 100644
--- a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx
+++ b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { wrapInTestApp } from '@backstage/test-utils';
import { LogViewer } from './LogViewer';
export default {
title: 'Data Display/LogViewer',
component: LogViewer,
- decorators: [(Story: ComponentType<{}>) => wrapInTestApp()],
+ decorators: [
+ (Story: ComponentType>) => wrapInTestApp(),
+ ],
};
const exampleLog = `Starting up task with 3 steps
diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx
index 8daf7b69b6..f647166250 100644
--- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx
+++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx
@@ -23,7 +23,7 @@ import MenuBookIcon from '@material-ui/icons/MenuBook';
import CloudQueueIcon from '@material-ui/icons/CloudQueue';
import AcUnitIcon from '@material-ui/icons/AcUnit';
import AppsIcon from '@material-ui/icons/Apps';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { SidebarPage } from './Page';
import { Sidebar } from './Bar';
import { SidebarGroup } from './SidebarGroup';
@@ -46,7 +46,7 @@ export default {
title: 'Layout/Sidebar',
component: Sidebar,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(, { mountedRoutes: { '/': routeRef } }),
],
};
diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md
index 52ee47f6bb..227b9b9816 100644
--- a/packages/core-plugin-api/api-report.md
+++ b/packages/core-plugin-api/api-report.md
@@ -12,6 +12,7 @@ import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api';
import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api';
import { JsonValue } from '@backstage/types';
import { Observable } from '@backstage/types';
+import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
@@ -137,14 +138,16 @@ export type ApiRefConfig = {
// @public
export type AppComponents = {
- NotFoundErrorPage: ComponentType<{}>;
+ NotFoundErrorPage: ComponentType>;
BootErrorPage: ComponentType;
- Progress: ComponentType<{}>;
- Router: ComponentType<{
- basename?: string;
- }>;
+ Progress: ComponentType>;
+ Router: ComponentType<
+ PropsWithChildren<{
+ basename?: string;
+ }>
+ >;
ErrorBoundaryFallback: ComponentType;
- ThemeProvider?: ComponentType<{}>;
+ ThemeProvider?: ComponentType>;
SignInPage?: ComponentType;
};
@@ -247,10 +250,10 @@ export const bitbucketServerAuthApiRef: ApiRef<
>;
// @public
-export type BootErrorPageProps = {
+export type BootErrorPageProps = PropsWithChildren<{
step: 'load-config' | 'load-chunk';
error: Error;
-};
+}>;
// @public
export type CommonAnalyticsContext = {
@@ -405,11 +408,11 @@ export type ErrorApiErrorContext = {
export const errorApiRef: ApiRef;
// @public
-export type ErrorBoundaryFallbackProps = {
+export type ErrorBoundaryFallbackProps = PropsWithChildren<{
plugin?: BackstagePlugin_2;
error: Error;
resetError: () => void;
-};
+}>;
// @public
export type Extension = {
@@ -690,9 +693,9 @@ export enum SessionState {
}
// @public
-export type SignInPageProps = {
+export type SignInPageProps = PropsWithChildren<{
onSignInSuccess(identityApi: IdentityApi_2): void;
-};
+}>;
// @public
export interface StorageApi {
diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx
index 056402f2a8..8d62f29c52 100644
--- a/packages/create-app/templates/default-app/packages/app/src/App.tsx
+++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx
@@ -40,12 +40,14 @@ const app = createApp({
bind(catalogPlugin.externalRoutes, {
createComponent: scaffolderPlugin.routes.root,
viewTechDoc: techdocsPlugin.routes.docRoot,
+ createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
});
bind(apiDocsPlugin.externalRoutes, {
registerApi: catalogImportPlugin.routes.importPage,
});
bind(scaffolderPlugin.externalRoutes, {
registerComponent: catalogImportPlugin.routes.importPage,
+ viewTechDoc: techdocsPlugin.routes.docRoot,
});
bind(orgPlugin.externalRoutes, {
catalogIndex: catalogPlugin.routes.catalogIndex,
diff --git a/packages/dev-utils/api-report.md b/packages/dev-utils/api-report.md
index e1cb93313f..7ff48db175 100644
--- a/packages/dev-utils/api-report.md
+++ b/packages/dev-utils/api-report.md
@@ -12,6 +12,7 @@ import { ComponentType } from 'react';
import { Entity } from '@backstage/catalog-model';
import { GridProps } from '@material-ui/core';
import { IconComponent } from '@backstage/core-plugin-api';
+import { PropsWithChildren } from 'react';
import { ReactNode } from 'react';
// @public
@@ -22,7 +23,7 @@ export class DevAppBuilder {
addPage(opts: DevAppPageOptions): DevAppBuilder;
addRootChild(node: ReactNode): DevAppBuilder;
addThemes(themes: AppTheme[]): this;
- build(): ComponentType<{}>;
+ build(): ComponentType>;
registerApi<
Api,
Impl extends Api,
diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx
index d2c91c9394..36e87855ed 100644
--- a/packages/dev-utils/src/devApp/render.tsx
+++ b/packages/dev-utils/src/devApp/render.tsx
@@ -44,7 +44,7 @@ import {
} from '@backstage/integration-react';
import { Box } from '@material-ui/core';
import BookmarkIcon from '@material-ui/icons/Bookmark';
-import React, { ComponentType, ReactNode } from 'react';
+import React, { ComponentType, ReactNode, PropsWithChildren } from 'react';
import ReactDOM from 'react-dom';
import { createRoutesFromChildren, Route } from 'react-router-dom';
import { SidebarThemeSwitcher } from './SidebarThemeSwitcher';
@@ -164,7 +164,7 @@ export class DevAppBuilder {
/**
* Build a DevApp component using the resources registered so far
*/
- build(): ComponentType<{}> {
+ build(): ComponentType> {
const fakeRouteRef = createRouteRef({ id: 'fake' });
const FakePage = () => Page belonging to another plugin.;
attachComponentData(FakePage, 'core.mountPoint', fakeRouteRef);
diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx
index e2938dc9fc..eda3597225 100644
--- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx
+++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx
@@ -30,7 +30,7 @@ describe('', () => {
const apiDocsConfig: jest.Mocked = {
getApiDefinitionWidget: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx
index 5476857a06..d249e59aeb 100644
--- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx
+++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx
@@ -24,7 +24,7 @@ describe('', () => {
const apiDocsConfig: jest.Mocked = {
getApiDefinitionWidget: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
index afb836ecc9..ea51d3989d 100644
--- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
@@ -39,7 +39,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx
index 85b353f04e..6ab55c5c66 100644
--- a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx
@@ -39,7 +39,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
index 40ffeff330..5f69e90f9f 100644
--- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
@@ -39,7 +39,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx
index abc91d8793..f7a18cf671 100644
--- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx
+++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx
@@ -35,7 +35,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx
index bdd45c3d58..8b003bf2e2 100644
--- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx
+++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx
@@ -35,7 +35,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx
index bee5587ea1..d50fdc964b 100644
--- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx
+++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import {
EntityPeekAheadPopover,
EntityPeekAheadPopoverProps,
@@ -80,7 +80,7 @@ const defaultArgs = {
export default {
title: 'Catalog /PeekAheadPopover',
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
<>
) =>
+ (Story: ComponentType>) =>
wrapInTestApp(, {
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx
index 176dd24530..7b94bd14df 100644
--- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx
+++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { EntityRefLinks, EntityRefLinksProps } from './EntityRefLinks';
import { wrapInTestApp } from '@backstage/test-utils';
import { CompoundEntityRef } from '@backstage/catalog-model';
@@ -27,7 +27,7 @@ const defaultArgs = {
export default {
title: 'Catalog /EntityRefLinks',
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(, {
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
diff --git a/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx
index eccad8e17f..f21b3cb710 100644
--- a/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx
+++ b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx
@@ -27,7 +27,7 @@ import { useStarredEntities } from './useStarredEntities';
describe('useStarredEntities', () => {
let mockApi: StarredEntitiesApi;
- let wrapper: React.ComponentType;
+ let wrapper: React.ComponentType>;
const mockEntity: Entity = {
apiVersion: '1',
diff --git a/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx
index ad168e5f9d..d24b27f903 100644
--- a/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx
+++ b/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx
@@ -27,7 +27,7 @@ describe('useStarredEntity', () => {
toggleStarred: jest.fn(),
starredEntitie$: jest.fn(),
};
- let wrapper: React.ComponentType;
+ let wrapper: React.ComponentType>;
beforeEach(() => {
wrapper = (props: PropsWithChildren<{}>) => (
diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md
index 970d8d3251..6bb4fcc4a1 100644
--- a/plugins/catalog/api-report.md
+++ b/plugins/catalog/api-report.md
@@ -105,6 +105,13 @@ export const catalogPlugin: BackstagePlugin<
},
true
>;
+ createFromTemplate: ExternalRouteRef<
+ {
+ namespace: string;
+ templateName: string;
+ },
+ true
+ >;
},
CatalogInputPluginOptions
>;
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index e76be49515..74f72e3631 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -40,6 +40,7 @@
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
+ "@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@backstage/theme": "workspace:^",
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
index 869d6f1757..e8d2657e9f 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
@@ -30,7 +30,7 @@ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { screen } from '@testing-library/react';
import React from 'react';
-import { viewTechDocRouteRef } from '../../routes';
+import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
import { AboutCard } from './AboutCard';
describe('', () => {
@@ -505,4 +505,165 @@ describe('', () => {
expect(screen.getByText('View TechDocs')).toBeVisible();
expect(screen.getByText('View TechDocs').closest('a')).toBeNull();
});
+
+ it('renders launch template link', async () => {
+ const entity = {
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ kind: 'Template',
+ metadata: {
+ name: 'create-react-app-template',
+ namespace: 'default',
+ },
+ };
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ '/create/templates/:namespace/:templateName':
+ createFromTemplateRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('Launch Template')).toBeVisible();
+ expect(screen.getByText('Launch Template').closest('a')).toHaveAttribute(
+ 'href',
+ '/create/templates/default/create-react-app-template',
+ );
+ });
+
+ it.each([
+ {
+ testName: 'entity is not a template',
+ entity: {
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ kind: 'Component',
+ metadata: {
+ name: 'create-react-app-template',
+ namespace: 'default',
+ },
+ },
+ },
+ {
+ testName: 'apiVersion is not scaffolder.backstage.io/v1beta3',
+ entity: {
+ apiVersion: 'v1',
+ kind: 'Template',
+ metadata: {
+ name: 'create-react-app-template',
+ namespace: 'default',
+ },
+ },
+ },
+ ])(
+ 'should not render launch template link when $testName',
+ async ({ entity }) => {
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ '/create/templates/:namespace/:templateName':
+ createFromTemplateRouteRef,
+ },
+ },
+ );
+
+ expect(screen.queryByText('Launch Template')).toBeNull();
+ },
+ );
+
+ it('renders disabled launch template link when route is not bound', async () => {
+ const entity = {
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ kind: 'Template',
+ metadata: {
+ name: 'create-react-app-template',
+ namespace: 'default',
+ },
+ };
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('Launch Template')).toBeVisible();
+ expect(screen.getByText('Launch Template').closest('a')).toBeNull();
+ });
});
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
index cb48be2b26..a08a9be5a4 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
import {
ANNOTATION_EDIT_URL,
ANNOTATION_LOCATION,
@@ -30,6 +29,7 @@ import {
alertApiRef,
errorApiRef,
useApi,
+ useApp,
useRouteRef,
} from '@backstage/core-plugin-api';
import {
@@ -41,6 +41,7 @@ import {
getEntitySourceLocation,
useEntity,
} from '@backstage/plugin-catalog-react';
+import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import {
Card,
CardContent,
@@ -49,11 +50,13 @@ import {
IconButton,
makeStyles,
} from '@material-ui/core';
+import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import CachedIcon from '@material-ui/icons/Cached';
import DocsIcon from '@material-ui/icons/Description';
import EditIcon from '@material-ui/icons/Edit';
import React, { useCallback } from 'react';
-import { viewTechDocRouteRef } from '../../routes';
+
+import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
import { AboutContent } from './AboutContent';
const useStyles = makeStyles({
@@ -90,6 +93,7 @@ export interface AboutCardProps {
*/
export function AboutCard(props: AboutCardProps) {
const { variant } = props;
+ const app = useApp();
const classes = useStyles();
const { entity } = useEntity();
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
@@ -97,6 +101,7 @@ export function AboutCard(props: AboutCardProps) {
const alertApi = useApi(alertApiRef);
const errorApi = useApi(errorApiRef);
const viewTechdocLink = useRouteRef(viewTechDocRouteRef);
+ const templateRoute = useRouteRef(createFromTemplateRouteRef);
const entitySourceLocation = getEntitySourceLocation(
entity,
@@ -126,6 +131,26 @@ export function AboutCard(props: AboutCardProps) {
}),
};
+ const subHeaderLinks = [viewInSource, viewInTechDocs];
+
+ if (isTemplateEntityV1beta3(entity)) {
+ const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon;
+
+ const launchTemplate: IconLinkVerticalProps = {
+ label: 'Launch Template',
+ icon: ,
+ disabled: !templateRoute,
+ href:
+ templateRoute &&
+ templateRoute({
+ templateName: entity.metadata.name,
+ namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,
+ }),
+ };
+
+ subHeaderLinks.push(launchTemplate);
+ }
+
let cardClass = '';
if (variant === 'gridItem') {
cardClass = classes.gridItemCard;
@@ -179,7 +204,7 @@ export function AboutCard(props: AboutCardProps) {
>
}
- subheader={}
+ subheader={}
/>
diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx
index 94a9a86b70..e5900d75e0 100644
--- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx
+++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx
@@ -28,7 +28,7 @@ import { DependencyOfComponentsCard } from './DependencyOfComponentsCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
index 657f95c60f..9255ee4f36 100644
--- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
@@ -28,7 +28,7 @@ import { DependsOnComponentsCard } from './DependsOnComponentsCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
index 5a77cd6ab4..3b0d7dcff3 100644
--- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
@@ -28,7 +28,7 @@ import { DependsOnResourcesCard } from './DependsOnResourcesCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx
index 9d46f2ed42..3651421797 100644
--- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx
+++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx
@@ -28,7 +28,7 @@ import { HasComponentsCard } from './HasComponentsCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
index 364d503a96..4bf6524a9c 100644
--- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
@@ -28,7 +28,7 @@ import { HasResourcesCard } from './HasResourcesCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx
index b76a0a68ec..97e69e93d1 100644
--- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx
+++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx
@@ -28,7 +28,7 @@ import { HasSubcomponentsCard } from './HasSubcomponentsCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx
index 634fe57e82..224e0c6703 100644
--- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx
+++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx
@@ -28,7 +28,7 @@ import { HasSystemsCard } from './HasSystemsCard';
describe('', () => {
const getEntities: jest.MockedFunction = jest.fn();
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts
index ea6ddcaff2..711f52767a 100644
--- a/plugins/catalog/src/plugin.ts
+++ b/plugins/catalog/src/plugin.ts
@@ -21,7 +21,11 @@ import {
entityRouteRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
-import { createComponentRouteRef, viewTechDocRouteRef } from './routes';
+import {
+ createComponentRouteRef,
+ createFromTemplateRouteRef,
+ viewTechDocRouteRef,
+} from './routes';
import {
createApiFactory,
createComponentExtension,
@@ -77,6 +81,7 @@ export const catalogPlugin = createPlugin({
externalRoutes: {
createComponent: createComponentRouteRef,
viewTechDoc: viewTechDocRouteRef,
+ createFromTemplate: createFromTemplateRouteRef,
},
__experimentalConfigure(
options?: CatalogInputPluginOptions,
diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts
index 5c0d195d74..ab70a3c551 100644
--- a/plugins/catalog/src/routes.ts
+++ b/plugins/catalog/src/routes.ts
@@ -30,6 +30,12 @@ export const viewTechDocRouteRef = createExternalRouteRef({
params: ['namespace', 'kind', 'name'],
});
+export const createFromTemplateRouteRef = createExternalRouteRef({
+ id: 'create-from-template',
+ optional: true,
+ params: ['namespace', 'templateName'],
+});
+
export const rootRouteRef = createRouteRef({
id: 'catalog',
});
diff --git a/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx b/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx
index 046e3e9860..52ddaa0f4e 100644
--- a/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx
+++ b/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx
@@ -26,7 +26,7 @@ describe('', () => {
getFindingSummary: jest.fn(),
getFindingSummaries: jest.fn(),
};
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx
index be07b4707f..ec78ac906b 100644
--- a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx
+++ b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx
@@ -38,7 +38,7 @@ describe('', () => {
getFindingSummary: jest.fn(),
getFindingSummaries: jest.fn(),
};
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
diff --git a/plugins/home/README.md b/plugins/home/README.md
index 42fbf73d18..8bb474aa4b 100644
--- a/plugins/home/README.md
+++ b/plugins/home/README.md
@@ -22,11 +22,9 @@ yarn add --cwd packages/app @backstage/plugin-home
```tsx
import React from 'react';
-export const HomePage = () => {
- return {
- /* TODO: Compose a Home Page here */
- };
-};
+export const homePage = (
+ /* TODO: Compose a Home Page here */
+);
```
2. Add a route where the homepage will live, presumably `/`.
@@ -35,11 +33,11 @@ export const HomePage = () => {
```tsx
import { HomepageCompositionRoot } from '@backstage/plugin-home';
-import { HomePage } from './components/home/HomePage';
+import { homePage } from './components/home/HomePage';
// ...
}>
-
+ {homePage}
;
// ...
```
@@ -72,15 +70,13 @@ import React from 'react';
import Grid from '@material-ui/core/Grid';
import { RandomJokeHomePageComponent } from '@backstage/plugin-home';
-export const HomePage = () => {
- return (
-
-
-
-
+export const homePage = (
+
+
+
- );
-};
+
+);
```
Additionally, the App Integrator is provided an escape hatch in case the way the card is rendered does not fit their requirements. They may optionally pass the `Renderer`-prop, which will receive the `title`, `content` and optionally `actions`, `settings` and `contextProvider`, if they exist for the component. This allows the App Integrator to render the content in any way they want.
@@ -102,18 +98,16 @@ import { HomePageSearchBar } from '@backstage/plugin-search';
import { HomePageCalendar } from '@backstage/plugin-gcalendar';
import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar';
-export const HomePage = () => {
- return (
-
- // Insert the allowed widgets inside the grid
-
-
-
-
-
-
- );
-};
+export const homePage = (
+
+ // Insert the allowed widgets inside the grid
+
+
+
+
+
+
+);
```
### Creating Customizable Components
diff --git a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx
index 741ad2e977..da5c318af2 100644
--- a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx
+++ b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx
@@ -21,12 +21,12 @@ import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
import { configApiRef } from '@backstage/core-plugin-api';
import { ConfigReader } from '@backstage/core-app-api';
import { Grid, makeStyles } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
export default {
title: 'Plugins/Home/Components/CompanyLogo',
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
) => wrapInTestApp()],
+ decorators: [
+ (Story: ComponentType>) => wrapInTestApp(),
+ ],
};
export const Default = () => {
diff --git a/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx b/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx
index a5e05e56c1..8172041f94 100644
--- a/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx
+++ b/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx
@@ -23,7 +23,7 @@ import {
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { Grid } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
const starredEntitiesApi = new MockStarredEntitiesApi();
starredEntitiesApi.toggleStarred('component:default/example-starred-entity');
@@ -73,7 +73,7 @@ const mockCatalogApi = {
export default {
title: 'Plugins/Home/Components/StarredEntities',
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
) => wrapInTestApp()],
+ decorators: [
+ (Story: ComponentType>) => wrapInTestApp(),
+ ],
};
export const Default = () => {
diff --git a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx
index ddcce2fe41..d7d12f02c6 100644
--- a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx
+++ b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx
@@ -16,12 +16,14 @@
import { Header } from '@backstage/core-components';
import { wrapInTestApp } from '@backstage/test-utils';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { WelcomeTitle } from './WelcomeTitle';
export default {
title: 'Plugins/Home/Components/WelcomeTitle',
- decorators: [(Story: ComponentType<{}>) => wrapInTestApp()],
+ decorators: [
+ (Story: ComponentType>) => wrapInTestApp(),
+ ],
};
export const Default = () => {
diff --git a/plugins/jenkins-backend/config.d.ts b/plugins/jenkins-backend/config.d.ts
index 3dda0a73c5..5b725dd801 100644
--- a/plugins/jenkins-backend/config.d.ts
+++ b/plugins/jenkins-backend/config.d.ts
@@ -38,11 +38,17 @@ export interface Config {
* instance.
*/
name: string;
-
baseUrl: string;
username: string;
/** @visibility secret */
apiKey: string;
+ extraRequestHeaders?: Partial<{
+ /** @visibility secret */
+ Authorization: string;
+ /** @visibility secret */
+ authorization: string;
+ [key: string]: string;
+ }>;
}[];
};
}
diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx
index 526d7c9c4c..e10d6322e0 100644
--- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx
+++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx
@@ -23,7 +23,7 @@ import {
} from '@backstage/plugin-catalog-react';
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
import { Grid } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { GroupProfileCard } from './GroupProfileCard';
const dummyDepartment = {
@@ -74,7 +74,7 @@ export default {
title: 'Plugins/Org/Group Profile Card',
component: GroupProfileCard,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx
index a27be7857b..02598691e0 100644
--- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx
+++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx
@@ -21,7 +21,7 @@ import {
} from '@backstage/plugin-catalog-react';
import { wrapInTestApp } from '@backstage/test-utils';
import { Grid } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { UserProfileCard } from './UserProfileCard';
const dummyGroup = {
@@ -94,7 +94,7 @@ export default {
title: 'Plugins/Org/User Profile Card',
component: UserProfileCard,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(, {
mountedRoutes: {
'/a': entityRouteRef,
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index 2affe1c423..c0ba5c8fe0 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -95,6 +95,30 @@ annotations:
pagerduty.com/integration-key: [INTEGRATION_KEY]
```
+### The homepage component
+
+You may also add the component to the homepage of Backstage. Edit the `HomePage.tsx` file, import `PagerDutyHomepageCard` and add it to the home page. e.g.
+
+```typescript jsx
+...
+export const homePage = (
+
+ ...
+
+
+ ...
+
+
+
+
+);
+```
+
Next, provide the [API token](https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key) that the client will use to make requests to the [PagerDuty API](https://developer.pagerduty.com/docs/rest-api-v2/rest-api/).
Add the proxy configuration in `app-config.yaml`:
diff --git a/plugins/pagerduty/api-report.md b/plugins/pagerduty/api-report.md
index b460086976..dd24aa9bec 100644
--- a/plugins/pagerduty/api-report.md
+++ b/plugins/pagerduty/api-report.md
@@ -7,6 +7,7 @@
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
+import { CardExtensionProps } from '@backstage/plugin-home-react';
import { ConfigApi } from '@backstage/core-plugin-api';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
@@ -14,7 +15,24 @@ import { FetchApi } from '@backstage/core-plugin-api';
import { ReactNode } from 'react';
// @public (undocumented)
-export const EntityPagerDutyCard: (props: PagerDutyCardProps) => JSX.Element;
+export const EntityPagerDutyCard: (
+ props: EntityPagerDutyCardProps,
+) => JSX.Element;
+
+// @public (undocumented)
+export type EntityPagerDutyCardProps = {
+ readOnly?: boolean;
+};
+
+// @public (undocumented)
+export const HomePagePagerDutyCard: (
+ props: CardExtensionProps,
+) => JSX.Element;
+
+// @public (undocumented)
+export type HomePagePagerDutyCardProps = PagerDutyEntity & {
+ readOnly?: boolean;
+};
// @public (undocumented)
const isPluginApplicableToEntity: (entity: Entity) => boolean;
@@ -31,6 +49,9 @@ export interface PagerDutyApi {
): Promise;
getOnCallByPolicyId(policyId: string): Promise;
getServiceByEntity(entity: Entity): Promise;
+ getServiceByPagerDutyEntity(
+ pagerDutyEntity: PagerDutyEntity,
+ ): Promise;
triggerAlarm(request: PagerDutyTriggerAlarmRequest): Promise;
}
@@ -44,13 +65,11 @@ export type PagerDutyAssignee = {
html_url: string;
};
-// @public (undocumented)
-export const PagerDutyCard: (props: PagerDutyCardProps) => JSX.Element;
+// @public @deprecated (undocumented)
+export const PagerDutyCard: (props: EntityPagerDutyCardProps) => JSX.Element;
-// @public (undocumented)
-export type PagerDutyCardProps = {
- readOnly?: boolean;
-};
+// @public @deprecated (undocumented)
+export type PagerDutyCardProps = EntityPagerDutyCardProps;
// @public (undocumented)
export type PagerDutyChangeEvent = {
@@ -98,6 +117,10 @@ export class PagerDutyClient implements PagerDutyApi {
// (undocumented)
getServiceByEntity(entity: Entity): Promise;
// (undocumented)
+ getServiceByPagerDutyEntity(
+ pagerDutyEntity: PagerDutyEntity,
+ ): Promise;
+ // (undocumented)
triggerAlarm(request: PagerDutyTriggerAlarmRequest): Promise;
}
diff --git a/plugins/pagerduty/dev/index.tsx b/plugins/pagerduty/dev/index.tsx
index e45e0ffd8f..4ed8e07263 100644
--- a/plugins/pagerduty/dev/index.tsx
+++ b/plugins/pagerduty/dev/index.tsx
@@ -15,136 +15,12 @@
*/
import React from 'react';
-import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { createDevApp } from '@backstage/dev-utils';
import { pagerDutyPlugin, EntityPagerDutyCard } from '../src/plugin';
import { pagerDutyApiRef } from '../src/api';
-import { PagerDutyApi, PagerDutyTriggerAlarmRequest } from '../src/api/types';
-import {
- PagerDutyIncident,
- PagerDutyChangeEvent,
-} from '../src/components/types';
-
-const mockEntity: Entity = {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'backstage',
- description: 'backstage.io',
- annotations: {
- 'github.com/project-slug': 'backstage/backstage',
- 'pagerduty.com/service-id': 'foo',
- 'pagerduty.com/integration-key': 'foo',
- },
- },
- spec: {
- lifecycle: 'production',
- type: 'website',
- owner: 'user:guest',
- },
-};
-
-const mockPagerDutyApi: PagerDutyApi = {
- async getServiceByEntity(entity: Entity) {
- return {
- service: {
- name: entity.metadata.name,
- integrationKey: 'key',
- id: '123',
- html_url: 'http://service',
- escalation_policy: {
- id: '123',
- html_url: 'http://escalationpolicy',
- user: {
- id: '123',
- summary: 'summary',
- email: 'email@email.com',
- html_url: 'http://user',
- name: 'some-user',
- },
- },
- },
- };
- },
-
- async getIncidentsByServiceId(serviceId: string) {
- const incident = (title: string) => {
- return {
- id: '123',
- title: title,
- status: 'acknowledged',
- html_url: 'http://incident',
- assignments: [
- {
- assignee: {
- id: '123',
- summary: 'Jane Doe',
- html_url: 'http://assignee',
- },
- },
- ],
- serviceId: serviceId,
- created_at: '2015-10-06T21:30:42Z',
- } as PagerDutyIncident;
- };
-
- return {
- incidents: [
- incident('Some Alerting Incident'),
- incident('Another Alerting Incident'),
- ],
- };
- },
-
- async getChangeEventsByServiceId(serviceId: string) {
- const changeEvent = (description: string) => {
- return {
- id: serviceId,
- source: 'some-source',
- html_url: 'http://changeevent',
- links: [
- {
- href: 'http://link',
- text: 'link text',
- },
- ],
- summary: description,
- timestamp: '2018-10-06T21:30:42Z',
- } as PagerDutyChangeEvent;
- };
-
- return {
- change_events: [
- changeEvent('us-east-1 deployment'),
- changeEvent('us-west-2 deployment'),
- ],
- };
- },
-
- async getOnCallByPolicyId() {
- const oncall = (name: string, escalation: number) => {
- return {
- user: {
- id: '123',
- name: name,
- html_url: 'http://assignee',
- summary: 'summary',
- email: 'email@email.com',
- },
- escalation_level: escalation,
- };
- };
-
- return {
- oncalls: [oncall('Jane Doe', 1), oncall('John Doe', 2)],
- };
- },
-
- async triggerAlarm(request: PagerDutyTriggerAlarmRequest) {
- return new Response(request.description);
- },
-};
+import { mockPagerDutyApi } from './mockPagerDutyApi';
+import { mockEntity } from './mockEntity';
createDevApp()
.registerApi({
diff --git a/plugins/pagerduty/dev/mockEntity.ts b/plugins/pagerduty/dev/mockEntity.ts
new file mode 100644
index 0000000000..251ba80706
--- /dev/null
+++ b/plugins/pagerduty/dev/mockEntity.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Entity } from '@backstage/catalog-model';
+
+export const mockEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'backstage',
+ description: 'backstage.io',
+ annotations: {
+ 'github.com/project-slug': 'backstage/backstage',
+ 'pagerduty.com/service-id': 'foo',
+ 'pagerduty.com/integration-key': 'foo',
+ },
+ },
+ spec: {
+ lifecycle: 'production',
+ type: 'website',
+ owner: 'user:guest',
+ },
+};
diff --git a/plugins/pagerduty/dev/mockPagerDutyApi.ts b/plugins/pagerduty/dev/mockPagerDutyApi.ts
new file mode 100644
index 0000000000..f8836d7043
--- /dev/null
+++ b/plugins/pagerduty/dev/mockPagerDutyApi.ts
@@ -0,0 +1,146 @@
+/*
+ * 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 {
+ PagerDutyApi,
+ PagerDutyChangeEvent,
+ PagerDutyEntity,
+ PagerDutyIncident,
+ PagerDutyTriggerAlarmRequest,
+} from '../src';
+import { Entity } from '@backstage/catalog-model';
+
+export const mockPagerDutyApi: PagerDutyApi = {
+ async getServiceByPagerDutyEntity(pagerDutyEntity: PagerDutyEntity) {
+ return {
+ service: {
+ name: pagerDutyEntity.name,
+ integrationKey: 'key',
+ id: '123',
+ html_url: 'http://service',
+ escalation_policy: {
+ id: '123',
+ html_url: 'http://escalationpolicy',
+ user: {
+ id: '123',
+ summary: 'summary',
+ email: 'email@email.com',
+ html_url: 'http://user',
+ name: 'some-user',
+ },
+ },
+ },
+ };
+ },
+
+ async getServiceByEntity(entity: Entity) {
+ return {
+ service: {
+ name: entity.metadata.name,
+ integrationKey: 'key',
+ id: '123',
+ html_url: 'http://service',
+ escalation_policy: {
+ id: '123',
+ html_url: 'http://escalationpolicy',
+ user: {
+ id: '123',
+ summary: 'summary',
+ email: 'email@email.com',
+ html_url: 'http://user',
+ name: 'some-user',
+ },
+ },
+ },
+ };
+ },
+
+ async getIncidentsByServiceId(serviceId: string) {
+ const incident = (title: string) => {
+ return {
+ id: '123',
+ title: title,
+ status: 'acknowledged',
+ html_url: 'http://incident',
+ assignments: [
+ {
+ assignee: {
+ id: '123',
+ summary: 'Jane Doe',
+ html_url: 'http://assignee',
+ },
+ },
+ ],
+ serviceId: serviceId,
+ created_at: '2015-10-06T21:30:42Z',
+ } as PagerDutyIncident;
+ };
+
+ return {
+ incidents: [
+ incident('Some Alerting Incident'),
+ incident('Another Alerting Incident'),
+ ],
+ };
+ },
+
+ async getChangeEventsByServiceId(serviceId: string) {
+ const changeEvent = (description: string) => {
+ return {
+ id: serviceId,
+ source: 'some-source',
+ html_url: 'http://changeevent',
+ links: [
+ {
+ href: 'http://link',
+ text: 'link text',
+ },
+ ],
+ summary: description,
+ timestamp: '2018-10-06T21:30:42Z',
+ } as PagerDutyChangeEvent;
+ };
+
+ return {
+ change_events: [
+ changeEvent('us-east-1 deployment'),
+ changeEvent('us-west-2 deployment'),
+ ],
+ };
+ },
+
+ async getOnCallByPolicyId() {
+ const oncall = (name: string, escalation: number) => {
+ return {
+ user: {
+ id: '123',
+ name: name,
+ html_url: 'http://assignee',
+ summary: 'summary',
+ email: 'email@email.com',
+ },
+ escalation_level: escalation,
+ };
+ };
+
+ return {
+ oncalls: [oncall('Jane Doe', 1), oncall('John Doe', 2)],
+ };
+ },
+
+ async triggerAlarm(request: PagerDutyTriggerAlarmRequest) {
+ return new Response(request.description);
+ },
+};
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 5437764465..dc40d566ce 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -38,6 +38,7 @@
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
+ "@backstage/plugin-home-react": "workspace:^",
"@backstage/theme": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index 7d99be219d..350b838951 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -30,6 +30,7 @@ import { createApiRef, ConfigApi } from '@backstage/core-plugin-api';
import { NotFoundError } from '@backstage/errors';
import { Entity } from '@backstage/catalog-model';
import { getPagerDutyEntity } from '../components/pagerDutyEntity';
+import { PagerDutyEntity } from '../types';
/** @public */
export class UnauthorizedError extends Error {}
@@ -63,8 +64,10 @@ export class PagerDutyClient implements PagerDutyApi {
constructor(private readonly config: PagerDutyClientApiConfig) {}
- async getServiceByEntity(entity: Entity): Promise {
- const { integrationKey, serviceId } = getPagerDutyEntity(entity);
+ async getServiceByPagerDutyEntity(
+ pagerDutyEntity: PagerDutyEntity,
+ ): Promise {
+ const { integrationKey, serviceId } = pagerDutyEntity;
let response: PagerDutyServiceResponse;
let url: string;
@@ -92,6 +95,9 @@ export class PagerDutyClient implements PagerDutyApi {
return response;
}
+ async getServiceByEntity(entity: Entity): Promise {
+ return await this.getServiceByPagerDutyEntity(getPagerDutyEntity(entity));
+ }
async getIncidentsByServiceId(
serviceId: string,
): Promise {
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
index f1971d53d5..a878afc447 100644
--- a/plugins/pagerduty/src/api/types.ts
+++ b/plugins/pagerduty/src/api/types.ts
@@ -22,6 +22,7 @@ import {
} from '../components/types';
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
+import { PagerDutyEntity } from '../types';
export type PagerDutyServicesResponse = {
services: PagerDutyService[];
@@ -57,6 +58,14 @@ export type PagerDutyTriggerAlarmRequest = {
/** @public */
export interface PagerDutyApi {
+ /**
+ * Fetches the service for the provided pager duty Entity.
+ *
+ */
+ getServiceByPagerDutyEntity(
+ pagerDutyEntity: PagerDutyEntity,
+ ): Promise;
+
/**
* Fetches the service for the provided Entity.
*
diff --git a/plugins/pagerduty/src/components/EntityPagerDutyCard/index.test.tsx b/plugins/pagerduty/src/components/EntityPagerDutyCard/index.test.tsx
new file mode 100644
index 0000000000..3f3a4ca1a8
--- /dev/null
+++ b/plugins/pagerduty/src/components/EntityPagerDutyCard/index.test.tsx
@@ -0,0 +1,386 @@
+/*
+ * 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 React from 'react';
+import { render, waitFor, fireEvent, act } from '@testing-library/react';
+import {
+ EntityPagerDutyCard,
+ isPluginApplicableToEntity,
+} from '../EntityPagerDutyCard';
+import { Entity } from '@backstage/catalog-model';
+import { EntityProvider } from '@backstage/plugin-catalog-react';
+import { NotFoundError } from '@backstage/errors';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
+import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../../api';
+import { PagerDutyService, PagerDutyUser } from '../types';
+
+import { alertApiRef } from '@backstage/core-plugin-api';
+import { ApiProvider } from '@backstage/core-app-api';
+
+const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'pagerduty-test',
+ annotations: {
+ 'pagerduty.com/integration-key': 'abc123',
+ },
+ },
+};
+
+const entityWithoutAnnotations: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'pagerduty-test',
+ annotations: {},
+ },
+};
+
+const entityWithServiceId: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'pagerduty-test',
+ annotations: {
+ 'pagerduty.com/service-id': 'def456',
+ },
+ },
+};
+
+const entityWithAllAnnotations: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'pagerduty-test',
+ annotations: {
+ 'pagerduty.com/integration-key': 'abc123',
+ 'pagerduty.com/service-id': 'def456',
+ },
+ },
+};
+
+const user: PagerDutyUser = {
+ name: 'person1',
+ id: 'p1',
+ summary: 'person1',
+ email: 'person1@example.com',
+ html_url: 'http://a.com/id1',
+};
+
+const service: PagerDutyService = {
+ id: 'def456',
+ name: 'pagerduty-name',
+ html_url: 'www.example.com',
+ escalation_policy: {
+ id: 'def',
+ user: user,
+ html_url: 'http://a.com/id1',
+ },
+ integrationKey: 'abc123',
+};
+
+const mockPagerDutyApi: Partial = {
+ getServiceByEntity: async () => ({ service }),
+ getServiceByPagerDutyEntity: async () => ({ service }),
+ getOnCallByPolicyId: async () => ({ oncalls: [] }),
+ getIncidentsByServiceId: async () => ({ incidents: [] }),
+};
+
+const apis = TestApiRegistry.from(
+ [pagerDutyApiRef, mockPagerDutyApi],
+ [alertApiRef, {}],
+);
+
+describe('isPluginApplicableToEntity', () => {
+ describe('when entity has no annotations', () => {
+ it('returns false', () => {
+ expect(isPluginApplicableToEntity(entityWithoutAnnotations)).toBe(false);
+ });
+ });
+
+ describe('when entity has the pagerduty.com/integration-key annotation', () => {
+ it('returns true', () => {
+ expect(isPluginApplicableToEntity(entity)).toBe(true);
+ });
+ });
+
+ describe('when entity has the pagerduty.com/service-id annotation', () => {
+ it('returns true', () => {
+ expect(isPluginApplicableToEntity(entityWithServiceId)).toBe(true);
+ });
+ });
+
+ describe('when entity has all annotations', () => {
+ it('returns true', () => {
+ expect(isPluginApplicableToEntity(entityWithAllAnnotations)).toBe(true);
+ });
+ });
+});
+
+describe('EntityPagerDutyCard', () => {
+ it('Render pagerduty', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockImplementationOnce(async () => ({ service }));
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Service Directory')).toBeInTheDocument();
+ expect(getByText('Create Incident')).toBeInTheDocument();
+ expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ });
+
+ it('Handles custom error for missing token', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockRejectedValueOnce(new UnauthorizedError());
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Missing or invalid PagerDuty Token')).toBeInTheDocument();
+ });
+
+ it('Handles custom NotFoundError', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockRejectedValueOnce(new NotFoundError());
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('PagerDuty Service Not Found')).toBeInTheDocument();
+ });
+
+ it('handles general error', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('An error occurred'));
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+
+ expect(
+ getByText(
+ 'Error encountered while fetching information. An error occurred',
+ ),
+ ).toBeInTheDocument();
+ });
+
+ it('opens the dialog when trigger button is clicked', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockImplementationOnce(async () => ({ service }));
+
+ const { getByText, queryByTestId, getByRole } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Service Directory')).toBeInTheDocument();
+
+ const triggerLink = getByText('Create Incident');
+ await act(async () => {
+ fireEvent.click(triggerLink);
+ });
+ expect(getByRole('dialog')).toBeInTheDocument();
+ });
+
+ describe('when entity has the pagerduty.com/service-id annotation', () => {
+ it('Renders PagerDuty service information', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockImplementationOnce(async () => ({ service }));
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Service Directory')).toBeInTheDocument();
+ expect(getByText('Create Incident')).toBeInTheDocument();
+ expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ });
+
+ it('Handles custom error for missing token', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockRejectedValueOnce(new UnauthorizedError());
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(
+ getByText('Missing or invalid PagerDuty Token'),
+ ).toBeInTheDocument();
+ });
+
+ it('Handles custom NotFoundError', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockRejectedValueOnce(new NotFoundError());
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('PagerDuty Service Not Found')).toBeInTheDocument();
+ });
+
+ it('handles general error', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('An error occurred'));
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+
+ expect(
+ getByText(
+ 'Error encountered while fetching information. An error occurred',
+ ),
+ ).toBeInTheDocument();
+ });
+
+ it('disables the Create Incident button', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockImplementationOnce(async () => ({ service }));
+
+ const { queryByTestId, getByTitle } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(
+ getByTitle('Must provide an integration-key to create incidents')
+ .className,
+ ).toMatch('disabled');
+ });
+ });
+
+ describe('when entity has all annotations', () => {
+ it('queries by integration key', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockImplementationOnce(async () => ({ service }));
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Service Directory')).toBeInTheDocument();
+ expect(getByText('Create Incident')).toBeInTheDocument();
+ expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ });
+ });
+
+ describe('when entity has all annotations but the plugin has been configured to be "read only"', () => {
+ it('queries by integration key but does not render the "Create Incident" button', async () => {
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
+ .fn()
+ .mockImplementationOnce(async () => ({ service }));
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Service Directory')).toBeInTheDocument();
+ expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ expect(() => getByText('Create Incident')).toThrow();
+ });
+ });
+});
diff --git a/plugins/pagerduty/src/components/EntityPagerDutyCard/index.tsx b/plugins/pagerduty/src/components/EntityPagerDutyCard/index.tsx
new file mode 100644
index 0000000000..50e6f3a096
--- /dev/null
+++ b/plugins/pagerduty/src/components/EntityPagerDutyCard/index.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 React from 'react';
+import { Entity } from '@backstage/catalog-model';
+import { PAGERDUTY_INTEGRATION_KEY, PAGERDUTY_SERVICE_ID } from '../constants';
+import { useEntity } from '@backstage/plugin-catalog-react';
+import { getPagerDutyEntity } from '../pagerDutyEntity';
+import { PagerDutyCard } from '../PagerDutyCard';
+
+/** @public */
+export const isPluginApplicableToEntity = (entity: Entity) =>
+ Boolean(
+ entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY] ||
+ entity.metadata.annotations?.[PAGERDUTY_SERVICE_ID],
+ );
+
+/** @public */
+export type EntityPagerDutyCardProps = {
+ readOnly?: boolean;
+};
+
+/** @public */
+export const EntityPagerDutyCard = (props: EntityPagerDutyCardProps) => {
+ const { readOnly } = props;
+ const { entity } = useEntity();
+ const pagerDutyEntity = getPagerDutyEntity(entity);
+ return ;
+};
diff --git a/plugins/pagerduty/src/components/Errors/ServiceNotFoundError.tsx b/plugins/pagerduty/src/components/Errors/ServiceNotFoundError.tsx
index d8b61ef7cd..236585735e 100644
--- a/plugins/pagerduty/src/components/Errors/ServiceNotFoundError.tsx
+++ b/plugins/pagerduty/src/components/Errors/ServiceNotFoundError.tsx
@@ -21,7 +21,7 @@ export const ServiceNotFoundError = () => (
{
+ return ;
+};
diff --git a/plugins/pagerduty/src/components/HomePagePagerDutyCard/index.ts b/plugins/pagerduty/src/components/HomePagePagerDutyCard/index.ts
new file mode 100644
index 0000000000..e9038852a6
--- /dev/null
+++ b/plugins/pagerduty/src/components/HomePagePagerDutyCard/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { Content } from './Content';
+export type { HomePagePagerDutyCardProps } from './Content';
diff --git a/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx
index 34c8914fde..6f4501450c 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx
@@ -15,9 +15,7 @@
*/
import React from 'react';
import { render, waitFor, fireEvent, act } from '@testing-library/react';
-import { PagerDutyCard, isPluginApplicableToEntity } from '../PagerDutyCard';
-import { Entity } from '@backstage/catalog-model';
-import { EntityProvider } from '@backstage/plugin-catalog-react';
+import { PagerDutyCard } from '../PagerDutyCard';
import { NotFoundError } from '@backstage/errors';
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../../api';
@@ -26,49 +24,6 @@ import { PagerDutyService, PagerDutyUser } from '../types';
import { alertApiRef } from '@backstage/core-plugin-api';
import { ApiProvider } from '@backstage/core-app-api';
-const entity: Entity = {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'pagerduty-test',
- annotations: {
- 'pagerduty.com/integration-key': 'abc123',
- },
- },
-};
-
-const entityWithoutAnnotations: Entity = {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'pagerduty-test',
- annotations: {},
- },
-};
-
-const entityWithServiceId: Entity = {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'pagerduty-test',
- annotations: {
- 'pagerduty.com/service-id': 'def456',
- },
- },
-};
-
-const entityWithAllAnnotations: Entity = {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'pagerduty-test',
- annotations: {
- 'pagerduty.com/integration-key': 'abc123',
- 'pagerduty.com/service-id': 'def456',
- },
- },
-};
-
const user: PagerDutyUser = {
name: 'person1',
id: 'p1',
@@ -100,44 +55,16 @@ const apis = TestApiRegistry.from(
[alertApiRef, {}],
);
-describe('isPluginApplicableToEntity', () => {
- describe('when entity has no annotations', () => {
- it('returns false', () => {
- expect(isPluginApplicableToEntity(entityWithoutAnnotations)).toBe(false);
- });
- });
-
- describe('when entity has the pagerduty.com/integration-key annotation', () => {
- it('returns true', () => {
- expect(isPluginApplicableToEntity(entity)).toBe(true);
- });
- });
-
- describe('when entity has the pagerduty.com/service-id annotation', () => {
- it('returns true', () => {
- expect(isPluginApplicableToEntity(entityWithServiceId)).toBe(true);
- });
- });
-
- describe('when entity has all annotations', () => {
- it('returns true', () => {
- expect(isPluginApplicableToEntity(entityWithAllAnnotations)).toBe(true);
- });
- });
-});
-
-describe('PageDutyCard', () => {
+describe('PagerDutyCard', () => {
it('Render pagerduty', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockImplementationOnce(async () => ({ service }));
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -149,16 +76,14 @@ describe('PageDutyCard', () => {
});
it('Handles custom error for missing token', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockRejectedValueOnce(new UnauthorizedError());
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -167,16 +92,14 @@ describe('PageDutyCard', () => {
});
it('Handles custom NotFoundError', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockRejectedValueOnce(new NotFoundError());
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -185,15 +108,13 @@ describe('PageDutyCard', () => {
});
it('handles general error', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockRejectedValueOnce(new Error('An error occurred'));
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -207,16 +128,14 @@ describe('PageDutyCard', () => {
});
it('opens the dialog when trigger button is clicked', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockImplementationOnce(async () => ({ service }));
const { getByText, queryByTestId, getByRole } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -232,16 +151,14 @@ describe('PageDutyCard', () => {
describe('when entity has the pagerduty.com/service-id annotation', () => {
it('Renders PagerDuty service information', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockImplementationOnce(async () => ({ service }));
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -253,16 +170,18 @@ describe('PageDutyCard', () => {
});
it('Handles custom error for missing token', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockRejectedValueOnce(new UnauthorizedError());
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -273,16 +192,18 @@ describe('PageDutyCard', () => {
});
it('Handles custom NotFoundError', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockRejectedValueOnce(new NotFoundError());
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -291,15 +212,17 @@ describe('PageDutyCard', () => {
});
it('handles general error', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockRejectedValueOnce(new Error('An error occurred'));
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -313,16 +236,14 @@ describe('PageDutyCard', () => {
});
it('disables the Create Incident button', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockImplementationOnce(async () => ({ service }));
const { queryByTestId, getByTitle } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -336,16 +257,18 @@ describe('PageDutyCard', () => {
describe('when entity has all annotations', () => {
it('queries by integration key', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockImplementationOnce(async () => ({ service }));
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
@@ -359,16 +282,19 @@ describe('PageDutyCard', () => {
describe('when entity has all annotations but the plugin has been configured to be "read only"', () => {
it('queries by integration key but does not render the "Create Incident" button', async () => {
- mockPagerDutyApi.getServiceByEntity = jest
+ mockPagerDutyApi.getServiceByPagerDutyEntity = jest
.fn()
.mockImplementationOnce(async () => ({ service }));
const { getByText, queryByTestId } = render(
wrapInTestApp(
-
-
-
+ ,
),
);
diff --git a/plugins/pagerduty/src/components/PagerDutyCard/index.tsx b/plugins/pagerduty/src/components/PagerDutyCard/index.tsx
index fd23166d88..bb89865c5f 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard/index.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard/index.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import React, { ReactNode, useState, useCallback } from 'react';
-import { Entity } from '@backstage/catalog-model';
import { Card, CardHeader, Divider, CardContent } from '@material-ui/core';
import { Incidents } from '../Incident';
import { EscalationPolicy } from '../Escalation';
@@ -25,7 +24,6 @@ import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
import { MissingTokenError, ServiceNotFoundError } from '../Errors';
import WebIcon from '@material-ui/icons/Web';
import DateRangeIcon from '@material-ui/icons/DateRange';
-import { PAGERDUTY_INTEGRATION_KEY, PAGERDUTY_SERVICE_ID } from '../constants';
import { TriggerDialog } from '../TriggerDialog';
import { ChangeEvents } from '../ChangeEvents';
@@ -39,30 +37,20 @@ import {
CardTab,
InfoCard,
} from '@backstage/core-components';
-import { useEntity } from '@backstage/plugin-catalog-react';
-import { getPagerDutyEntity } from '../pagerDutyEntity';
+import { PagerDutyEntity } from '../../types';
const BasicCard = ({ children }: { children: ReactNode }) => (
{children}
);
/** @public */
-export const isPluginApplicableToEntity = (entity: Entity) =>
- Boolean(
- entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY] ||
- entity.metadata.annotations?.[PAGERDUTY_SERVICE_ID],
- );
-
-/** @public */
-export type PagerDutyCardProps = {
+export type PagerDutyCardProps = PagerDutyEntity & {
readOnly?: boolean;
};
/** @public */
export const PagerDutyCard = (props: PagerDutyCardProps) => {
- const { readOnly } = props;
- const { entity } = useEntity();
- const pagerDutyEntity = getPagerDutyEntity(entity);
+ const { readOnly, integrationKey, name } = props;
const api = useApi(pagerDutyApiRef);
const [refreshIncidents, setRefreshIncidents] = useState(false);
const [refreshChangeEvents, setRefreshChangeEvents] =
@@ -86,7 +74,9 @@ export const PagerDutyCard = (props: PagerDutyCardProps) => {
loading,
error,
} = useAsync(async () => {
- const { service: foundService } = await api.getServiceByEntity(entity);
+ const { service: foundService } = await api.getServiceByPagerDutyEntity(
+ props,
+ );
return {
id: foundService.id,
@@ -137,7 +127,7 @@ export const PagerDutyCard = (props: PagerDutyCardProps) => {
* There is no guarantee the current user entity has a valid email association, so instead just
* only allow triggering incidents when an integration key is present.
*/
- const createIncidentDisabled = !pagerDutyEntity.integrationKey;
+ const createIncidentDisabled = !integrationKey;
const triggerLink: IconLinkVerticalProps = {
label: 'Create Incident',
onClick: showDialog,
@@ -195,6 +185,8 @@ export const PagerDutyCard = (props: PagerDutyCardProps) => {
showDialog={dialogShown}
handleDialog={hideDialog}
onIncidentCreated={handleRefresh}
+ name={name}
+ integrationKey={integrationKey}
/>
)}
>
diff --git a/plugins/pagerduty/src/components/TriggerButton/index.tsx b/plugins/pagerduty/src/components/TriggerButton/index.tsx
index 732298c6d8..1c12103d73 100644
--- a/plugins/pagerduty/src/components/TriggerButton/index.tsx
+++ b/plugins/pagerduty/src/components/TriggerButton/index.tsx
@@ -33,7 +33,7 @@ const useStyles = makeStyles(theme => ({
/** @public */
export function TriggerButton(props: { children?: ReactNode }) {
const { buttonStyle } = useStyles();
- const { integrationKey } = usePagerdutyEntity();
+ const { integrationKey, name } = usePagerdutyEntity();
const [dialogShown, setDialogShown] = useState(false);
const showDialog = useCallback(() => {
@@ -57,7 +57,12 @@ export function TriggerButton(props: { children?: ReactNode }) {
: 'Missing integration key'}
{integrationKey && (
-
+
)}
>
);
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index c6874cc7f3..74cd5100fa 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -17,8 +17,6 @@ import React from 'react';
import { fireEvent, act } from '@testing-library/react';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { pagerDutyApiRef } from '../../api';
-import { Entity } from '@backstage/catalog-model';
-import { EntityProvider } from '@backstage/plugin-catalog-react';
import { TriggerDialog } from './TriggerDialog';
import { ApiProvider } from '@backstage/core-app-api';
@@ -49,26 +47,15 @@ describe('TriggerDialog', () => {
);
it('open the dialog and trigger an alarm', async () => {
- const entity: Entity = {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'pagerduty-test',
- annotations: {
- 'pagerduty.com/integration-key': 'abc123',
- },
- },
- };
-
const { getByText, getByRole, getByTestId } = await renderInTestApp(
-
- {}}
- onIncidentCreated={() => {}}
- />
-
+ {}}
+ onIncidentCreated={() => {}}
+ />
,
);
@@ -89,8 +76,7 @@ describe('TriggerDialog', () => {
});
expect(mockTriggerAlarmFn).toHaveBeenCalled();
expect(mockTriggerAlarmFn).toHaveBeenCalledWith({
- integrationKey:
- entity!.metadata!.annotations!['pagerduty.com/integration-key'],
+ integrationKey: 'abc123',
source: window.location.toString(),
description,
userName: 'guest',
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
index 4dc4f04699..a1e88b8fa6 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
@@ -28,7 +28,6 @@ import {
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { pagerDutyApiRef } from '../../api';
import { Alert } from '@material-ui/lab';
-import { usePagerdutyEntity } from '../../hooks';
import {
useApi,
alertApiRef,
@@ -40,14 +39,17 @@ type Props = {
showDialog: boolean;
handleDialog: () => void;
onIncidentCreated?: () => void;
+ name: string;
+ integrationKey: string;
};
export const TriggerDialog = ({
showDialog,
handleDialog,
onIncidentCreated: onIncidentCreated,
+ name,
+ integrationKey,
}: Props) => {
- const { name, integrationKey } = usePagerdutyEntity();
const alertApi = useApi(alertApiRef);
const identityApi = useApi(identityApiRef);
const api = useApi(pagerDutyApiRef);
diff --git a/plugins/pagerduty/src/components/index.ts b/plugins/pagerduty/src/components/index.ts
index e7c1ff4e72..264d7195e9 100644
--- a/plugins/pagerduty/src/components/index.ts
+++ b/plugins/pagerduty/src/components/index.ts
@@ -23,12 +23,13 @@ export type {
PagerDutyUser,
} from './types';
-export type { PagerDutyCardProps } from './PagerDutyCard';
+export type { EntityPagerDutyCardProps } from './EntityPagerDutyCard';
+export type { HomePagePagerDutyCardProps } from './HomePagePagerDutyCard';
export {
isPluginApplicableToEntity,
isPluginApplicableToEntity as isPagerDutyAvailable,
- PagerDutyCard,
-} from './PagerDutyCard';
+ EntityPagerDutyCard,
+} from './EntityPagerDutyCard';
export { TriggerButton } from './TriggerButton';
diff --git a/plugins/pagerduty/src/deprecated.ts b/plugins/pagerduty/src/deprecated.ts
new file mode 100644
index 0000000000..e9f60f6894
--- /dev/null
+++ b/plugins/pagerduty/src/deprecated.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 { EntityPagerDutyCardProps, EntityPagerDutyCard } from './components';
+
+/**
+ * @public
+ * @deprecated Please use EntityPagerDutyCard
+ */
+export const PagerDutyCard = EntityPagerDutyCard;
+
+/**
+ * @public
+ * @deprecated Please use EntityPagerDutyCardProps
+ */
+export type PagerDutyCardProps = EntityPagerDutyCardProps;
diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts
index 2e0eddc9a8..c6f7aef89a 100644
--- a/plugins/pagerduty/src/index.ts
+++ b/plugins/pagerduty/src/index.ts
@@ -24,9 +24,12 @@ export {
pagerDutyPlugin,
pagerDutyPlugin as plugin,
EntityPagerDutyCard,
+ HomePagePagerDutyCard,
} from './plugin';
export * from './components';
+
export * from './api';
+export * from './deprecated';
export type { PagerDutyEntity } from './types';
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
index 73e7e0b397..59cc5cd8cc 100644
--- a/plugins/pagerduty/src/plugin.ts
+++ b/plugins/pagerduty/src/plugin.ts
@@ -23,6 +23,8 @@ import {
configApiRef,
createComponentExtension,
} from '@backstage/core-plugin-api';
+import { createCardExtension } from '@backstage/plugin-home-react';
+import { HomePagePagerDutyCardProps } from './components/HomePagePagerDutyCard/Content';
export const rootRouteRef = createRouteRef({
id: 'pagerduty',
@@ -51,7 +53,38 @@ export const EntityPagerDutyCard = pagerDutyPlugin.provide(
name: 'EntityPagerDutyCard',
component: {
lazy: () =>
- import('./components/PagerDutyCard').then(m => m.PagerDutyCard),
+ import('./components/EntityPagerDutyCard').then(
+ m => m.EntityPagerDutyCard,
+ ),
+ },
+ }),
+);
+
+/** @public */
+export const HomePagePagerDutyCard = pagerDutyPlugin.provide(
+ createCardExtension({
+ name: 'HomePagePagerDutyCard',
+ title: 'PagerDuty Homepage Card',
+ components: () => import('./components/HomePagePagerDutyCard'),
+ settings: {
+ schema: {
+ title: 'PagerDuty',
+ type: 'object',
+ properties: {
+ integrationKey: {
+ title: 'PagerDuty integration key',
+ type: 'string',
+ },
+ serviceId: {
+ title: 'PagerDuty service id',
+ type: 'string',
+ },
+ name: {
+ title: 'PagerDuty service name',
+ type: 'string',
+ },
+ },
+ },
},
}),
);
diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts
index 59fc0dc5a6..410ff86a2e 100644
--- a/plugins/proxy-backend/config.d.ts
+++ b/plugins/proxy-backend/config.d.ts
@@ -31,7 +31,17 @@ export interface Config {
/**
* Object with extra headers to be added to target requests.
*/
- headers?: { [key: string]: string };
+ headers?: Partial<{
+ /** @visibility secret */
+ Authorization: string;
+ /** @visibility secret */
+ authorization: string;
+ /** @visibility secret */
+ 'X-Api-Key': string;
+ /** @visibility secret */
+ 'x-api-key': string;
+ [key: string]: string;
+ }>;
/**
* Changes the origin of the host header to the target URL. Default: true.
*/
diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md
index f7cf6024ec..eccada5d51 100644
--- a/plugins/scaffolder-backend-module-gitlab/api-report.md
+++ b/plugins/scaffolder-backend-module-gitlab/api-report.md
@@ -26,8 +26,8 @@ export const createGitlabProjectAccessTokenAction: (options: {
integrations: ScmIntegrationRegistry;
}) => TemplateAction<
{
- projectId: string | number;
repoUrl: string;
+ projectId: string | number;
token?: string | undefined;
name?: string | undefined;
accessLevel?: number | undefined;
@@ -44,8 +44,8 @@ export const createGitlabProjectDeployTokenAction: (options: {
}) => TemplateAction<
{
name: string;
- projectId: string | number;
repoUrl: string;
+ projectId: string | number;
token?: string | undefined;
username?: string | undefined;
scopes?: string[] | undefined;
@@ -63,8 +63,8 @@ export const createGitlabProjectVariableAction: (options: {
{
key: string;
value: string;
- projectId: string | number;
repoUrl: string;
+ projectId: string | number;
variableType: string;
token?: string | undefined;
variableProtected?: boolean | undefined;
diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md
index 1761c88edf..82610a2fc3 100644
--- a/plugins/scaffolder-react/api-report.md
+++ b/plugins/scaffolder-react/api-report.md
@@ -207,7 +207,9 @@ export interface ScaffolderGetIntegrationsListResponse {
}
// @public
-export const ScaffolderLayouts: React.ComponentType;
+export const ScaffolderLayouts: React_2.ComponentType<
+ React_2.PropsWithChildren<{}>
+>;
// @public (undocumented)
export type ScaffolderOutputLink = {
diff --git a/plugins/scaffolder-react/src/layouts/createScaffolderLayout.ts b/plugins/scaffolder-react/src/layouts/createScaffolderLayout.ts
index 328f366007..6b2ae1b33b 100644
--- a/plugins/scaffolder-react/src/layouts/createScaffolderLayout.ts
+++ b/plugins/scaffolder-react/src/layouts/createScaffolderLayout.ts
@@ -17,6 +17,7 @@
import { LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from './keys';
import { attachComponentData, Extension } from '@backstage/core-plugin-api';
import type { FormProps as SchemaFormProps } from '@rjsf/core-v5';
+import React from 'react';
/**
* The field template from `@rjsf/core` which is a react component that gets passed `@rjsf/core` field related props.
@@ -67,7 +68,8 @@ export function createScaffolderLayout(
*
* @public
*/
-export const ScaffolderLayouts: React.ComponentType = (): JSX.Element | null =>
- null;
+export const ScaffolderLayouts: React.ComponentType<
+ React.PropsWithChildren<{}>
+> = (): JSX.Element | null => null;
attachComponentData(ScaffolderLayouts, LAYOUTS_WRAPPER_KEY, true);
diff --git a/plugins/scaffolder/alpha-api-report.md b/plugins/scaffolder/alpha-api-report.md
index 6ceacdc008..de657c2138 100644
--- a/plugins/scaffolder/alpha-api-report.md
+++ b/plugins/scaffolder/alpha-api-report.md
@@ -27,7 +27,7 @@ export type NextRouterProps = {
TemplateCardComponent?: React_2.ComponentType<{
template: TemplateEntityV1beta3;
}>;
- TaskPageComponent?: React_2.ComponentType<{}>;
+ TaskPageComponent?: React_2.ComponentType>;
TemplateOutputsComponent?: React_2.ComponentType<{
output?: ScaffolderTaskOutput;
}>;
diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md
index 3495522310..e50395be05 100644
--- a/plugins/scaffolder/api-report.md
+++ b/plugins/scaffolder/api-report.md
@@ -29,6 +29,7 @@ import { ListActionsResponse as ListActionsResponse_2 } from '@backstage/plugin-
import { LogEvent as LogEvent_2 } from '@backstage/plugin-scaffolder-react';
import { Observable } from '@backstage/types';
import { PathParams } from '@backstage/core-plugin-api';
+import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
@@ -382,7 +383,7 @@ export type RouterProps = {
template: TemplateEntityV1beta3;
}>
| undefined;
- TaskPageComponent?: ComponentType<{}>;
+ TaskPageComponent?: ComponentType>;
};
groups?: Array<{
title?: React_2.ReactNode;
@@ -466,7 +467,9 @@ export type ScaffolderGetIntegrationsListResponse =
ScaffolderGetIntegrationsListResponse_2;
// @public @deprecated (undocumented)
-export const ScaffolderLayouts: ComponentType<{}>;
+export const ScaffolderLayouts: ComponentType<{
+ children?: ReactNode;
+}>;
// @public @deprecated (undocumented)
export type ScaffolderOutputlink = ScaffolderOutputLink;
diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx
index 05d5cc9f0a..3ceda359cc 100644
--- a/plugins/scaffolder/src/components/Router.tsx
+++ b/plugins/scaffolder/src/components/Router.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType, useEffect } from 'react';
+import React, { ComponentType, useEffect, PropsWithChildren } from 'react';
import { Navigate, Route, Routes, useOutlet } from 'react-router-dom';
import { Entity } from '@backstage/catalog-model';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
@@ -52,7 +52,7 @@ export type RouterProps = {
TemplateCardComponent?:
| ComponentType<{ template: TemplateEntityV1beta3 }>
| undefined;
- TaskPageComponent?: ComponentType<{}>;
+ TaskPageComponent?: ComponentType>;
};
groups?: Array<{
title?: React.ReactNode;
diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
index 598616aaa3..cbc587a732 100644
--- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
@@ -49,7 +49,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
entities = [
diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
index 00508593f5..1d843ea8a2 100644
--- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
@@ -55,7 +55,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
beforeEach(() => {
entities = [
diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx
index 9c50d0d979..2061cd1243 100644
--- a/plugins/scaffolder/src/next/Router/Router.tsx
+++ b/plugins/scaffolder/src/next/Router/Router.tsx
@@ -58,7 +58,7 @@ export type NextRouterProps = {
TemplateCardComponent?: React.ComponentType<{
template: TemplateEntityV1beta3;
}>;
- TaskPageComponent?: React.ComponentType<{}>;
+ TaskPageComponent?: React.ComponentType>;
TemplateOutputsComponent?: React.ComponentType<{
output?: ScaffolderTaskOutput;
}>;
diff --git a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx
index 6db7955ec8..0eaece87bb 100644
--- a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx
+++ b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { Grid } from '@material-ui/core';
import LabelIcon from '@material-ui/icons/Label';
@@ -31,7 +31,7 @@ export default {
title: 'Plugins/Search/SearchAutocomplete',
component: SearchAutocomplete,
decorators: [
- (Story: ComponentType<{}>) => (
+ (Story: ComponentType>) => (
diff --git a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx
index 40a3c5cdca..f671546efa 100644
--- a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx
+++ b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx
@@ -30,7 +30,7 @@ export default {
title: 'Plugins/Search/SearchAutocompleteDefaultOption',
component: SearchAutocompleteDefaultOption,
decorators: [
- (Story: ComponentType<{}>) => (
+ (Story: ComponentType>) => (
diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx
index 05a46d11e1..f342eed211 100644
--- a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx
+++ b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { Grid, makeStyles } from '@material-ui/core';
import { TestApiProvider } from '@backstage/test-utils';
@@ -28,7 +28,7 @@ export default {
title: 'Plugins/Search/SearchBar',
component: SearchBar,
decorators: [
- (Story: ComponentType<{}>) => (
+ (Story: ComponentType>) => (
diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx
index 2c8a629f01..505c1e13c3 100644
--- a/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx
+++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { Grid, Paper } from '@material-ui/core';
import { TestApiProvider } from '@backstage/test-utils';
@@ -27,7 +27,7 @@ export default {
title: 'Plugins/Search/SearchFilter',
component: SearchFilter,
decorators: [
- (Story: ComponentType<{}>) => (
+ (Story: ComponentType>) => (
diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx
index 4e72c0fa25..dfb4e7ea91 100644
--- a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx
+++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { Grid } from '@material-ui/core';
import { TestApiProvider } from '@backstage/test-utils';
@@ -28,7 +28,7 @@ export default {
title: 'Plugins/Search/SearchPagination',
component: SearchPagination,
decorators: [
- (Story: ComponentType<{}>) => (
+ (Story: ComponentType>) => (
diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx
index f3086fc40a..a64d053dae 100644
--- a/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx
+++ b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { List, ListItem } from '@material-ui/core';
import DefaultIcon from '@material-ui/icons/InsertDriveFile';
@@ -70,7 +70,7 @@ export default {
title: 'Plugins/Search/SearchResult',
component: SearchResult,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx
index 40f7d30ff4..2f497d4568 100644
--- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx
+++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-import React, { ComponentType, useCallback, useState } from 'react';
+import React, {
+ ComponentType,
+ useCallback,
+ useState,
+ PropsWithChildren,
+} from 'react';
import {
Grid,
@@ -72,7 +77,7 @@ export default {
title: 'Plugins/Search/SearchResultGroup',
component: SearchResultGroup,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx
index 4369b6636e..d822d0f52d 100644
--- a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx
+++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ComponentType, useState } from 'react';
+import React, { ComponentType, useState, PropsWithChildren } from 'react';
import { Grid, ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
@@ -59,7 +59,7 @@ export default {
title: 'Plugins/Search/SearchResultList',
component: SearchResultList,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx
index 9a10aaa41b..c1b1f0df87 100644
--- a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx
+++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx
@@ -18,12 +18,12 @@ import { rootRouteRef, HomePageSearchBar } from '../../plugin';
import { searchApiRef } from '@backstage/plugin-search-react';
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
import { Grid, makeStyles } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
export default {
title: 'Plugins/Home/Components/SearchBar',
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
<>
) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
) => (
+ (Story: ComponentType>) => (
diff --git a/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx b/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx
index aed1ed25aa..9f0fd75341 100644
--- a/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx
+++ b/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx
@@ -19,7 +19,7 @@ import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
import { configApiRef } from '@backstage/core-plugin-api';
import { ConfigReader } from '@backstage/config';
import { Grid } from '@material-ui/core';
-import React, { ComponentType } from 'react';
+import React, { ComponentType, PropsWithChildren } from 'react';
import { StackOverflowIcon } from '../../icons';
import { stackOverflowApiRef } from '../../api';
@@ -46,7 +46,7 @@ export default {
title: 'Plugins/Home/Components/StackOverflow',
component: HomePageStackOverflowQuestions,
decorators: [
- (Story: ComponentType<{}>) =>
+ (Story: ComponentType>) =>
wrapInTestApp(
<>
) => wrapInTestApp()],
+ decorators: [
+ (Story: ComponentType>) => wrapInTestApp(),
+ ],
};
export const Default = () => {
diff --git a/plugins/techdocs/src/reader/components/useReaderState.test.tsx b/plugins/techdocs/src/reader/components/useReaderState.test.tsx
index 08bfa8f9ad..5f3e2d9662 100644
--- a/plugins/techdocs/src/reader/components/useReaderState.test.tsx
+++ b/plugins/techdocs/src/reader/components/useReaderState.test.tsx
@@ -26,7 +26,7 @@ import {
} from './useReaderState';
describe('useReaderState', () => {
- let Wrapper: React.ComponentType;
+ let Wrapper: React.ComponentType>;
const techdocsStorageApi: jest.Mocked = {
getApiOrigin: jest.fn(),
diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx b/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx
index dc4eeb85be..2b1f427be3 100644
--- a/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx
+++ b/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { FC } from 'react';
+import React, { FC, PropsWithChildren } from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { ConfigReader } from '@backstage/core-app-api';
@@ -31,7 +31,7 @@ const configApiMock: ConfigApi = new ConfigReader({
},
});
-const wrapper: FC = ({ children }) => (
+const wrapper: FC> = ({ children }) => (
{children}
diff --git a/yarn.lock b/yarn.lock
index 07c79358be..712fd10e6f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5868,6 +5868,7 @@ __metadata:
"@backstage/plugin-catalog-common": "workspace:^"
"@backstage/plugin-catalog-react": "workspace:^"
"@backstage/plugin-permission-react": "workspace:^"
+ "@backstage/plugin-scaffolder-common": "workspace:^"
"@backstage/plugin-search-common": "workspace:^"
"@backstage/plugin-search-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
@@ -7889,6 +7890,7 @@ __metadata:
"@backstage/dev-utils": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/plugin-catalog-react": "workspace:^"
+ "@backstage/plugin-home-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
"@material-ui/core": ^4.12.2
@@ -11079,8 +11081,8 @@ __metadata:
linkType: hard
"@google-cloud/storage@npm:^6.0.0":
- version: 6.9.5
- resolution: "@google-cloud/storage@npm:6.9.5"
+ version: 6.10.1
+ resolution: "@google-cloud/storage@npm:6.10.1"
dependencies:
"@google-cloud/paginator": ^3.0.7
"@google-cloud/projectify": ^3.0.0
@@ -11099,7 +11101,7 @@ __metadata:
retry-request: ^5.0.0
teeny-request: ^8.0.0
uuid: ^8.0.0
- checksum: eff663cc44ee553fbd2f62b67ef422783f29210c67f55c86c13e63a40c36f3c9b78e076b2a08bb6a74a9fdc452de9240aac91e0fa11c3a4f782416a230d88b74
+ checksum: 8908500e3057456ccac5cca17224807898a5c51c0423dec336e802ed0bc0785be7658b0050f848102a2f9207dbf3fb2649f6cc8e515c49fce3a134a8f158ecea
languageName: node
linkType: hard
@@ -12488,17 +12490,17 @@ __metadata:
linkType: hard
"@kubernetes-models/apimachinery@npm:^1.1.0":
- version: 1.1.1
- resolution: "@kubernetes-models/apimachinery@npm:1.1.1"
+ version: 1.2.0
+ resolution: "@kubernetes-models/apimachinery@npm:1.2.0"
dependencies:
- "@kubernetes-models/base": ^4.0.1
- "@kubernetes-models/validate": ^3.0.1
+ "@kubernetes-models/base": ^4.0.2
+ "@kubernetes-models/validate": ^3.1.0
tslib: ^2.4.0
- checksum: 915d6a55b485335622d43c6e1f1031ea949bfb3e985deba6389462b4c0a3b3030e71e6eebfc1e7b894afee543de2e5a1af0ff6694225a372e220f7dccb5b6eed
+ checksum: ac93980b732239517af47e0cb512e28fc320ed8348eaa8a68f65b16adc98cf90b6b36d9a9752d825e6e1ac84ae0c0d37e2dc8c8de140011506753a058c89bcf2
languageName: node
linkType: hard
-"@kubernetes-models/base@npm:^4.0.0, @kubernetes-models/base@npm:^4.0.1":
+"@kubernetes-models/base@npm:^4.0.0, @kubernetes-models/base@npm:^4.0.1, @kubernetes-models/base@npm:^4.0.2":
version: 4.0.2
resolution: "@kubernetes-models/base@npm:4.0.2"
dependencies:
@@ -12509,7 +12511,7 @@ __metadata:
languageName: node
linkType: hard
-"@kubernetes-models/validate@npm:^3.0.0, @kubernetes-models/validate@npm:^3.0.1, @kubernetes-models/validate@npm:^3.1.0":
+"@kubernetes-models/validate@npm:^3.0.0, @kubernetes-models/validate@npm:^3.1.0":
version: 3.1.0
resolution: "@kubernetes-models/validate@npm:3.1.0"
dependencies:
@@ -12999,9 +13001,9 @@ __metadata:
linkType: hard
"@microsoft/microsoft-graph-types@npm:^2.25.0, @microsoft/microsoft-graph-types@npm:^2.6.0":
- version: 2.25.0
- resolution: "@microsoft/microsoft-graph-types@npm:2.25.0"
- checksum: b311385e0a51f827082af5a016f35b1d6cd4d861121e8767b3279ab1a17475977cddbbdcc208abdddf0f953fd6ce4634d3c302e317cf50af4cb6111385347b4f
+ version: 2.33.0
+ resolution: "@microsoft/microsoft-graph-types@npm:2.33.0"
+ checksum: 9a07e258750cfdedebdc05670c9951fe507ba0868839a411ff36592015660a547d1e4fd9abec1533da979238ef674ce2bf4c363ab0ae2082818313fba0978ecb
languageName: node
linkType: hard
@@ -14221,18 +14223,18 @@ __metadata:
linkType: hard
"@react-hookz/web@npm:^23.0.0":
- version: 23.0.0
- resolution: "@react-hookz/web@npm:23.0.0"
+ version: 23.0.1
+ resolution: "@react-hookz/web@npm:23.0.1"
dependencies:
"@react-hookz/deep-equal": ^1.0.4
peerDependencies:
- js-cookie: ^3.0.1
+ js-cookie: ^3.0.5
react: ^16.8 || ^17 || ^18
react-dom: ^16.8 || ^17 || ^18
peerDependenciesMeta:
js-cookie:
optional: true
- checksum: 230bff62291bcafa65e0b9adcc3f0769fd63fabc226c77149e7797d1ea67362e9ad581ceff3cdfe2949698798d2deefb223c05bdfed1e465b5d92298d48cf3e5
+ checksum: 321770570c947321ba44de6ecee17337e4bb0c2a0c9d0dff5cf29e999faa2b7c1132c72a15aface46da9a709b715042ab48a30085d2a8feefba91aac96e82037
languageName: node
linkType: hard
@@ -21473,11 +21475,11 @@ __metadata:
linkType: hard
"cron@npm:^2.0.0":
- version: 2.3.0
- resolution: "cron@npm:2.3.0"
+ version: 2.3.1
+ resolution: "cron@npm:2.3.1"
dependencies:
luxon: ^3.2.1
- checksum: 70b220f3fc4b6bcd594f7f2c690bbf7a3a03b0e081eb0119f8e3ca21635f2da07d89e038d561d4c0742c8b0dd04977f25e307d0608455ef9f527a556b2da92fc
+ checksum: ac27c257cb8dba4b51978b4767119fc79bfc0dfa35ef0be2533bc4142ae006130515236e2102d04ee3ce44bffc2a4e37a3bee18a2152bbb303bf5ff944e25204
languageName: node
linkType: hard
@@ -32820,9 +32822,9 @@ __metadata:
linkType: hard
"openapi-types@npm:^12.0.0":
- version: 12.1.1
- resolution: "openapi-types@npm:12.1.1"
- checksum: 881dbc514e22046eb790c81370e912ca2daf5beec13cde77e8a15b5ec2635140487abdebf681ec969777067bcbb56b3c0880a0a22aa236d075d8fca5da9535ad
+ version: 12.1.3
+ resolution: "openapi-types@npm:12.1.3"
+ checksum: 7fa5547f87a58d2aa0eba6e91d396f42d7d31bc3ae140e61b5d60b47d2fd068b48776f42407d5a8da7280cf31195aa128c2fc285e8bb871d1105edee5647a0bb
languageName: node
linkType: hard