From 2890f47517645ebbb465f117127bf25ffd002e2e Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Wed, 23 Nov 2022 16:32:42 +0100
Subject: [PATCH 001/156] add changes to make it multiproject
Signed-off-by: Marc Bruins
---
.changeset/ninety-hats-serve.md | 5 +++++
docs/integrations/azure/discovery.md | 7 ++++++-
plugins/catalog-backend-module-azure/src/lib/azure.ts | 7 +++++--
.../src/providers/AzureDevOpsEntityProvider.ts | 11 ++++++-----
4 files changed, 22 insertions(+), 8 deletions(-)
create mode 100644 .changeset/ninety-hats-serve.md
diff --git a/.changeset/ninety-hats-serve.md b/.changeset/ninety-hats-serve.md
new file mode 100644
index 0000000000..637d56d47e
--- /dev/null
+++ b/.changeset/ninety-hats-serve.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-azure': minor
+---
+
+This will add the ability to use Azure DevOps with multi project with a single value which is a new feature as previously this had to be done manually for each project that you wanted to add
diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md
index 2156f60814..18686ebdc3 100644
--- a/docs/integrations/azure/discovery.md
+++ b/docs/integrations/azure/discovery.md
@@ -47,6 +47,11 @@ catalog:
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
+ yourSecondProviderId: # identifies your dataset / provider independent of config changes
+ organization: myorg
+ project: '*' # this will match all projects
+ repository: '*' # this will match all repos
+ path: /catalog-info.yaml
anotherProviderId: # another identifier
organization: myorg
project: myproject
@@ -62,7 +67,7 @@ The parameters available are:
- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host.
- **`organization:`** Your Organization slug (or Collection for on-premise users). Required.
-- **`project:`** Your project slug. Required.
+- **`project:`** Your project slug. Required. Wildcards are supported as show on the examples above. If not set, all projects will be searched.
- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched.
- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml.
- **`schedule`** _(optional)_:
diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.ts b/plugins/catalog-backend-module-azure/src/lib/azure.ts
index 316c9b0b8c..0c96d133d3 100644
--- a/plugins/catalog-backend-module-azure/src/lib/azure.ts
+++ b/plugins/catalog-backend-module-azure/src/lib/azure.ts
@@ -31,6 +31,9 @@ export interface CodeSearchResultItem {
repository: {
name: string;
};
+ project: {
+ name: string;
+ };
}
const isCloud = (host: string) => host === 'dev.azure.com';
@@ -47,7 +50,7 @@ export async function codeSearch(
const searchBaseUrl = isCloud(azureConfig.host)
? 'https://almsearch.dev.azure.com'
: `https://${azureConfig.host}`;
- const searchUrl = `${searchBaseUrl}/${org}/${project}/_apis/search/codesearchresults?api-version=6.0-preview.1`;
+ const searchUrl = `${searchBaseUrl}/${org}/_apis/search/codesearchresults?api-version=6.0-preview.1`;
let items: CodeSearchResultItem[] = [];
let hasMorePages = true;
@@ -59,7 +62,7 @@ export async function codeSearch(
}),
method: 'POST',
body: JSON.stringify({
- searchText: `path:${path} repo:${repo || '*'}`,
+ searchText: `path:${path} repo:${repo || '*'} proj:${project || '*'}`,
$skip: items.length,
$top: PAGE_SIZE,
}),
diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts
index b824d176ee..87ddde78d3 100644
--- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts
+++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts
@@ -166,17 +166,18 @@ export class AzureDevOpsEntityProvider implements EntityProvider {
}
private createLocationSpec(file: CodeSearchResultItem): LocationSpec {
+ const target = this.createObjectUrl(file);
+
return {
type: 'url',
- target: this.createObjectUrl(file),
+ target: target,
presence: 'required',
};
}
private createObjectUrl(file: CodeSearchResultItem): string {
- const baseUrl = `https://${this.config.host}/${this.config.organization}/${this.config.project}`;
- return encodeURI(
- `${baseUrl}/_git/${file.repository.name}?path=${file.path}`,
- );
+ const baseUrl = `https://${this.config.host}/${this.config.organization}`;
+ const encodedUri = `${baseUrl}/${file.project.name}/_git/${file.repository.name}?path=${file.path}`;
+ return encodedUri;
}
}
From 68787714bf8a23e43f59a5b32ef66910995b8fa6 Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Fri, 25 Nov 2022 06:38:20 +0100
Subject: [PATCH 002/156] add project to tests
Signed-off-by: Marc Bruins
---
.../src/lib/azure.test.ts | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
index 16f5067197..7c3fc0f9e3 100644
--- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
+++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
@@ -64,6 +64,9 @@ describe('azure', () => {
repository: {
name: 'backstage',
},
+ project: {
+ name: '*',
+ },
},
{
fileName: 'catalog-info.yaml',
@@ -71,6 +74,9 @@ describe('azure', () => {
repository: {
name: 'ios-app',
},
+ project: {
+ name: '*',
+ },
},
],
};
@@ -151,6 +157,9 @@ describe('azure', () => {
repository: {
name: 'backstage',
},
+ project: {
+ name: '*',
+ },
},
],
};
@@ -190,6 +199,9 @@ describe('azure', () => {
repository: {
name: 'backstage',
},
+ project: {
+ name: '*',
+ },
}));
};
From 73beead7480466607d9abe83c97aa204722f07d2 Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Fri, 25 Nov 2022 06:44:47 +0100
Subject: [PATCH 003/156] add project to tests expects
Signed-off-by: Marc Bruins
---
.../catalog-backend-module-azure/src/lib/azure.test.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
index 7c3fc0f9e3..14cd1d082f 100644
--- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
+++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
@@ -33,7 +33,7 @@ describe('azure', () => {
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
expect(req.body).toEqual({
- searchText: 'path:/catalog-info.yaml repo:*',
+ searchText: 'path:/catalog-info.yaml repo:* proj:*',
$skip: 0,
$top: 1000,
});
@@ -87,7 +87,7 @@ describe('azure', () => {
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
expect(req.body).toEqual({
- searchText: 'path:/catalog-info.yaml repo:*',
+ searchText: 'path:/catalog-info.yaml repo:* proj:*',
$skip: 0,
$top: 1000,
});
@@ -127,7 +127,7 @@ describe('azure', () => {
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
expect(req.body).toEqual({
- searchText: 'path:/catalog-info.yaml repo:backstage',
+ searchText: 'path:/catalog-info.yaml repo:backstage: proj:*',
$skip: 0,
$top: 1000,
});
@@ -170,7 +170,7 @@ describe('azure', () => {
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
expect(req.body).toEqual({
- searchText: 'path:/catalog-info.yaml repo:*',
+ searchText: 'path:/catalog-info.yaml repo:* proj:*',
$skip: 0,
$top: 1000,
});
From 206b9062a0d155f8cf833abe6d9496f89b5c2196 Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Fri, 25 Nov 2022 07:29:48 +0100
Subject: [PATCH 004/156] remove unnecessary ,
Signed-off-by: Marc Bruins
---
plugins/catalog-backend-module-azure/src/lib/azure.test.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
index 14cd1d082f..ec6ef654b6 100644
--- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
+++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
@@ -65,7 +65,7 @@ describe('azure', () => {
name: 'backstage',
},
project: {
- name: '*',
+ name: 'backstage',
},
},
{
@@ -75,7 +75,7 @@ describe('azure', () => {
name: 'ios-app',
},
project: {
- name: '*',
+ name: 'backstage',
},
},
],
@@ -87,7 +87,7 @@ describe('azure', () => {
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
expect(req.body).toEqual({
- searchText: 'path:/catalog-info.yaml repo:* proj:*',
+ searchText: 'path:/catalog-info.yaml repo:* proj:backstage',
$skip: 0,
$top: 1000,
});
From bf91c9f896dd8de3eae3e23a6326a32d48be4560 Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Tue, 20 Dec 2022 20:06:35 -0600
Subject: [PATCH 005/156] refactor: add LinkButton in favor of Button
Signed-off-by: Kurt King
---
.../src/components/Button/Button.tsx | 4 +-
.../LinkButton/LinkButton.stories.tsx | 170 ++++++++++++++++++
.../components/LinkButton/LinkButton.test.tsx | 44 +++++
.../src/components/LinkButton/LinkButton.tsx | 53 ++++++
.../src/components/LinkButton/index.ts | 17 ++
.../core-components/src/components/index.ts | 1 +
6 files changed, 287 insertions(+), 2 deletions(-)
create mode 100644 packages/core-components/src/components/LinkButton/LinkButton.stories.tsx
create mode 100644 packages/core-components/src/components/LinkButton/LinkButton.test.tsx
create mode 100644 packages/core-components/src/components/LinkButton/LinkButton.tsx
create mode 100644 packages/core-components/src/components/LinkButton/index.ts
diff --git a/packages/core-components/src/components/Button/Button.tsx b/packages/core-components/src/components/Button/Button.tsx
index 575f42b03c..771d94adb5 100644
--- a/packages/core-components/src/components/Button/Button.tsx
+++ b/packages/core-components/src/components/Button/Button.tsx
@@ -25,6 +25,7 @@ import { Link, LinkProps } from '../Link';
*
* @public
* @remarks
+ * @deprecated use `LinkButtonProps` instead
*
* See {@link https://v4.mui.com/api/button/#props | Material-UI Button Props} for all properties
*/
@@ -43,8 +44,7 @@ const LinkWrapper = React.forwardRef((props, ref) => (
*
* @public
* @remarks
- *
- * Makes the Button to utilize react-router
+ * @deprecated use `LinkButton` instead
*/
export const Button = React.forwardRef((props, ref) => (
diff --git a/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx b/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx
new file mode 100644
index 0000000000..3dc49abb7f
--- /dev/null
+++ b/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx
@@ -0,0 +1,170 @@
+/*
+ * 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, { ComponentType } from 'react';
+import { LinkButton } from './LinkButton';
+import { useLocation } from 'react-router-dom';
+import { createRouteRef, useRouteRef } from '@backstage/core-plugin-api';
+import Divider from '@material-ui/core/Divider';
+import List from '@material-ui/core/List';
+import ListItem from '@material-ui/core/ListItem';
+import ListItemText from '@material-ui/core/ListItemText';
+import Typography from '@material-ui/core/Typography';
+import MaterialButton from '@material-ui/core/Button';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { Link } from '../Link';
+
+const routeRef = createRouteRef({
+ id: 'storybook.test-route',
+});
+
+const Location = () => {
+ const location = useLocation();
+ return
Current location: {location.pathname}
;
+};
+
+export default {
+ title: 'Inputs/Button',
+ component: LinkButton,
+ decorators: [
+ (Story: ComponentType<{}>) =>
+ wrapInTestApp(
+ <>
+
+ A collection of buttons that should be used in the Backstage
+ interface. These leverage the properties inherited from{' '}
+
+ Material-UI Button
+
+ , but include an opinionated set that align to the Backstage design.
+
+
+
+
+
+
+
+
+
+
+ >,
+ { mountedRoutes: { '/hello': routeRef } },
+ ),
+ ],
+};
+
+export const Default = () => {
+ const link = useRouteRef(routeRef);
+ // Design Permutations:
+ // color = default | primary | secondary
+ // variant = contained | outlined | text
+ return (
+
+
+
+ Default Button:
+ This is the default button design which should be used in most cases.
+
+
color="primary" variant="contained"
+
+
+
+ Register Component
+
+
+
+
+ Secondary Button:
+ Used for actions that cancel, skip, and in general perform negative
+ functions, etc.
+
+
color="secondary" variant="contained"
+
+
+
+ Cancel
+
+
+
+
+ Tertiary Button:
+ Used commonly in a ButtonGroup and when the button function itself is
+ not a primary function on a page.
+
+
color="default" variant="outlined"
+
+
+
+ View Details
+
+
+
+ );
+};
+
+export const ButtonLinks = () => {
+ const link = useRouteRef(routeRef);
+
+ const handleClick = () => {
+ return 'Your click worked!';
+ };
+
+ return (
+ <>
+
+ {
+ // TODO: Refactor to use new routing mechanisms
+ }
+
+
+ Route Ref
+
+ has props for both Material-UI's component as well as for
+ react-router-dom's Route object.
+
+
+
+
+ Static Path
+
+ links to a statically defined route. In general, this should be
+ avoided.
+
+
+
+
+ View URL
+
+ links to a defined URL using Material-UI's Button.
+
+
+
+
+ Trigger Event
+
+ triggers an onClick event using Material-UI's Button.
+
+
+ >
+ );
+};
diff --git a/packages/core-components/src/components/LinkButton/LinkButton.test.tsx b/packages/core-components/src/components/LinkButton/LinkButton.test.tsx
new file mode 100644
index 0000000000..7dbc42f9cb
--- /dev/null
+++ b/packages/core-components/src/components/LinkButton/LinkButton.test.tsx
@@ -0,0 +1,44 @@
+/*
+ * 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, fireEvent, act } from '@testing-library/react';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { LinkButton } from './LinkButton';
+import { Route, Routes } from 'react-router-dom';
+
+describe('', () => {
+ it('navigates using react-router', async () => {
+ const testString = 'This is test string';
+ const linkButtonLabel = 'Navigate!';
+ const { getByText } = render(
+ wrapInTestApp(
+ <>
+ {linkButtonLabel}
+
+ {testString}
} />
+
+ >,
+ ),
+ );
+
+ expect(() => getByText(testString)).toThrow();
+ await act(async () => {
+ fireEvent.click(getByText(linkButtonLabel));
+ });
+ expect(getByText(testString)).toBeInTheDocument();
+ });
+});
diff --git a/packages/core-components/src/components/LinkButton/LinkButton.tsx b/packages/core-components/src/components/LinkButton/LinkButton.tsx
new file mode 100644
index 0000000000..60c218ba5f
--- /dev/null
+++ b/packages/core-components/src/components/LinkButton/LinkButton.tsx
@@ -0,0 +1,53 @@
+/*
+ * 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 MaterialButton, {
+ ButtonProps as MaterialButtonProps,
+} from '@material-ui/core/Button';
+import React from 'react';
+import { Link, LinkProps } from '../Link';
+
+/**
+ * Properties for {@link LinkButton}
+ *
+ * @public
+ * @remarks
+ *
+ * See {@link https://v4.mui.com/api/button/#props | Material-UI Button Props} for all properties
+ */
+export type LinkButtonProps = MaterialButtonProps &
+ Omit;
+
+/**
+ * This wrapper is here to reset the color of the Link and make typescript happy.
+ */
+const LinkWrapper = React.forwardRef((props, ref) => (
+
+));
+
+/**
+ * Thin wrapper on top of material-ui's {@link https://v4.mui.com/components/buttons/ | Button} component
+ *
+ * @public
+ * @remarks
+ *
+ * Makes the Button to utilize react-router
+ */
+export const LinkButton = React.forwardRef(
+ (props, ref) => (
+
+ ),
+) as (props: LinkButtonProps) => JSX.Element;
diff --git a/packages/core-components/src/components/LinkButton/index.ts b/packages/core-components/src/components/LinkButton/index.ts
new file mode 100644
index 0000000000..8ca699ecdd
--- /dev/null
+++ b/packages/core-components/src/components/LinkButton/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+export { LinkButton } from './LinkButton';
+export type { LinkButtonProps } from './LinkButton';
diff --git a/packages/core-components/src/components/index.ts b/packages/core-components/src/components/index.ts
index 473fab8444..218df9043d 100644
--- a/packages/core-components/src/components/index.ts
+++ b/packages/core-components/src/components/index.ts
@@ -30,6 +30,7 @@ export * from './HeaderIconLinkRow';
export * from './HorizontalScrollGrid';
export * from './Lifecycle';
export * from './Link';
+export * from './LinkButton';
export * from './LogViewer';
export * from './MarkdownContent';
export * from './OAuthRequestDialog';
From 910015f5b75e8aced02452e694032dce7bb7b13c Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Tue, 20 Dec 2022 20:23:23 -0600
Subject: [PATCH 006/156] docs: add changeset
Signed-off-by: Kurt King
---
.changeset/popular-dancers-join.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/popular-dancers-join.md
diff --git a/.changeset/popular-dancers-join.md b/.changeset/popular-dancers-join.md
new file mode 100644
index 0000000000..e7f6ab524f
--- /dev/null
+++ b/.changeset/popular-dancers-join.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+The Button component has been deprecated in favor of the LinkButton component
From 247bb8af2ab77bc990a53dfe80c0a8d546cad17d Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Tue, 20 Dec 2022 20:36:06 -0600
Subject: [PATCH 007/156] update api-report
Signed-off-by: Kurt King
---
packages/core-components/api-report.md | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md
index c46b3637cb..6dff7e35e0 100644
--- a/packages/core-components/api-report.md
+++ b/packages/core-components/api-report.md
@@ -129,10 +129,10 @@ export type BreadcrumbsStyledBoxClassKey = 'root';
// @public
export function BrokenImageIcon(props: IconComponentProps): JSX.Element;
-// @public
+// @public @deprecated
export const Button: (props: ButtonProps) => JSX.Element;
-// @public
+// @public @deprecated
export type ButtonProps = ButtonProps_2 & Omit;
// @public (undocumented)
@@ -629,6 +629,13 @@ export function LinearGauge(props: Props_11): JSX.Element | null;
// @public
export const Link: (props: LinkProps) => JSX.Element;
+// @public
+export const LinkButton: (props: LinkButtonProps) => JSX.Element;
+
+// @public
+export type LinkButtonProps = ButtonProps_2 &
+ Omit;
+
// Warning: (ae-missing-release-tag) "LinkProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
From cc0333d73cba7e3c2b97eed9d78fc23722ac7fb9 Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Tue, 20 Dec 2022 20:52:03 -0600
Subject: [PATCH 008/156] chore: update to 2022
Signed-off-by: Kurt King
---
packages/core-components/src/components/Link/Link.stories.tsx | 2 +-
packages/core-components/src/components/Link/Link.test.tsx | 2 +-
packages/core-components/src/components/Link/Link.tsx | 2 +-
packages/core-components/src/components/Link/index.ts | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/core-components/src/components/Link/Link.stories.tsx b/packages/core-components/src/components/Link/Link.stories.tsx
index 6049ec68b1..17e331b0b5 100644
--- a/packages/core-components/src/components/Link/Link.stories.tsx
+++ b/packages/core-components/src/components/Link/Link.stories.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Backstage Authors
+ * Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx
index 3538b79052..6365c00faf 100644
--- a/packages/core-components/src/components/Link/Link.test.tsx
+++ b/packages/core-components/src/components/Link/Link.test.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Backstage Authors
+ * Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx
index 440fcb811f..29214fb69b 100644
--- a/packages/core-components/src/components/Link/Link.tsx
+++ b/packages/core-components/src/components/Link/Link.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Backstage Authors
+ * Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/core-components/src/components/Link/index.ts b/packages/core-components/src/components/Link/index.ts
index 2160508451..d5bca4728f 100644
--- a/packages/core-components/src/components/Link/index.ts
+++ b/packages/core-components/src/components/Link/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Backstage Authors
+ * Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
From 3825687f5aed23c17e434315f77cc068ef71d58e Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Sat, 24 Dec 2022 18:43:24 +0100
Subject: [PATCH 009/156] Update docs and fix tests
Signed-off-by: Marc Bruins
---
docs/integrations/azure/discovery.md | 2 +-
.../providers/AzureDevOpsEntityProvider.test.ts | 15 +++++++++++++++
.../src/providers/AzureDevOpsEntityProvider.ts | 7 ++++---
3 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md
index 18686ebdc3..a7ddd9c4a9 100644
--- a/docs/integrations/azure/discovery.md
+++ b/docs/integrations/azure/discovery.md
@@ -149,7 +149,7 @@ When using a custom pattern, the target is composed of five parts:
- The base instance URL, `https://dev.azure.com` in this case
- The organization name which is required, `myorg` in this case
-- The project name which is required, `myproject` in this case
+- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to.
- The repository blob to scan, which accepts \* wildcard tokens and must be
added after `_git/`. This can simply be `*` to scan all repositories in the
project.
diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts
index f7a63f259e..dff1164899 100644
--- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts
@@ -164,6 +164,9 @@ describe('AzureDevOpsEntityProvider', () => {
repository: {
name: 'myrepo',
},
+ project: {
+ name: 'myproject',
+ },
},
],
'https://dev.azure.com/myorganization/myproject',
@@ -189,6 +192,9 @@ describe('AzureDevOpsEntityProvider', () => {
repository: {
name: 'myrepo',
},
+ project: {
+ name: 'myproject',
+ },
},
{
fileName: 'catalog-info.yaml',
@@ -196,6 +202,9 @@ describe('AzureDevOpsEntityProvider', () => {
repository: {
name: 'myotherrepo',
},
+ project: {
+ name: 'myproject',
+ },
},
],
'https://dev.azure.com/myorganization/myproject',
@@ -277,6 +286,9 @@ describe('AzureDevOpsEntityProvider', () => {
repository: {
name: 'myrepo',
},
+ project: {
+ name: 'myproject',
+ },
},
{
fileName: 'catalog-info.yaml',
@@ -284,6 +296,9 @@ describe('AzureDevOpsEntityProvider', () => {
repository: {
name: 'myotherrepo',
},
+ project: {
+ name: 'myproject',
+ },
},
],
'https://dev.azure.com/myorganization/myproject',
diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts
index 87ddde78d3..c9ad3fb05b 100644
--- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts
+++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts
@@ -176,8 +176,9 @@ export class AzureDevOpsEntityProvider implements EntityProvider {
}
private createObjectUrl(file: CodeSearchResultItem): string {
- const baseUrl = `https://${this.config.host}/${this.config.organization}`;
- const encodedUri = `${baseUrl}/${file.project.name}/_git/${file.repository.name}?path=${file.path}`;
- return encodedUri;
+ const baseUrl = `https://${this.config.host}/${this.config.organization}/${file.project.name}`;
+ return encodeURI(
+ `${baseUrl}/_git/${file.repository.name}?path=${file.path}`,
+ );
}
}
From 21c64d53bb11791bc3b06cb7221c17fa741d0df7 Mon Sep 17 00:00:00 2001
From: Ryan Hanchett
Date: Tue, 17 Jan 2023 14:30:28 -0800
Subject: [PATCH 010/156] feat: enable passing material table options to docs
table via entity list docs table
Signed-off-by: Ryan Hanchett
---
packages/core-components/src/components/Table/Table.tsx | 2 ++
plugins/techdocs/src/home/components/Tables/DocsTable.tsx | 5 ++++-
.../src/home/components/Tables/EntityListDocsTable.tsx | 5 ++++-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx
index c799402cbc..89a1d69b96 100644
--- a/packages/core-components/src/components/Table/Table.tsx
+++ b/packages/core-components/src/components/Table/Table.tsx
@@ -240,6 +240,8 @@ export interface TableProps
onStateChange?: (state: TableState) => any;
}
+export interface TableOptions extends Options {}
+
export function TableToolbar(toolbarProps: {
toolbarRef: MutableRefObject;
setSearch: (value: string) => void;
diff --git a/plugins/techdocs/src/home/components/Tables/DocsTable.tsx b/plugins/techdocs/src/home/components/Tables/DocsTable.tsx
index 883c496191..842c80018f 100644
--- a/plugins/techdocs/src/home/components/Tables/DocsTable.tsx
+++ b/plugins/techdocs/src/home/components/Tables/DocsTable.tsx
@@ -29,6 +29,7 @@ import {
EmptyState,
Table,
TableColumn,
+ TableOptions,
TableProps,
} from '@backstage/core-components';
import { actionFactories } from './actions';
@@ -47,6 +48,7 @@ export type DocsTableProps = {
loading?: boolean | undefined;
columns?: TableColumn[];
actions?: TableProps['actions'];
+ options?: TableOptions;
};
/**
@@ -55,7 +57,7 @@ export type DocsTableProps = {
* @public
*/
export const DocsTable = (props: DocsTableProps) => {
- const { entities, title, loading, columns, actions } = props;
+ const { entities, title, loading, columns, actions, options } = props;
const [, copyToClipboard] = useCopyToClipboard();
const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
const config = useApi(configApiRef);
@@ -102,6 +104,7 @@ export const DocsTable = (props: DocsTableProps) => {
pageSize: 20,
search: true,
actionsColumnIndex: -1,
+ ...options,
}}
data={documents}
columns={columns || defaultColumns}
diff --git a/plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx b/plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx
index d47ddb13c4..0042cc9418 100644
--- a/plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx
+++ b/plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx
@@ -20,6 +20,7 @@ import { capitalize } from 'lodash';
import {
CodeSnippet,
TableColumn,
+ TableOptions,
TableProps,
WarningPanel,
} from '@backstage/core-components';
@@ -40,6 +41,7 @@ import { DocsTableRow } from './types';
export type EntityListDocsTableProps = {
columns?: TableColumn[];
actions?: TableProps['actions'];
+ options?: TableOptions;
};
/**
@@ -48,7 +50,7 @@ export type EntityListDocsTableProps = {
* @public
*/
export const EntityListDocsTable = (props: EntityListDocsTableProps) => {
- const { columns, actions } = props;
+ const { columns, actions, options } = props;
const { loading, error, entities, filters } = useEntityList();
const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
const [, copyToClipboard] = useCopyToClipboard();
@@ -81,6 +83,7 @@ export const EntityListDocsTable = (props: EntityListDocsTableProps) => {
loading={loading}
actions={actions || defaultActions}
columns={columns}
+ options={options}
/>
);
};
From fd93c6b859add484a7855a6b3d3a26524df6a2c7 Mon Sep 17 00:00:00 2001
From: Ryan Hanchett
Date: Tue, 17 Jan 2023 14:36:08 -0800
Subject: [PATCH 011/156] fix: add missing export of table options to table
component index
Signed-off-by: Ryan Hanchett
---
packages/core-components/src/components/Table/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/core-components/src/components/Table/index.ts b/packages/core-components/src/components/Table/index.ts
index 7fc245d6d0..00e1ae9de5 100644
--- a/packages/core-components/src/components/Table/index.ts
+++ b/packages/core-components/src/components/Table/index.ts
@@ -22,6 +22,7 @@ export type {
TableColumn,
TableFilter,
TableProps,
+ TableOptions,
TableState,
TableClassKey,
FiltersContainerClassKey,
From 20840b36b4d503f588a22e9e6b3b54c9db3052ce Mon Sep 17 00:00:00 2001
From: Ryan Hanchett
Date: Tue, 17 Jan 2023 14:40:16 -0800
Subject: [PATCH 012/156] chore: add changesets
Signed-off-by: Ryan Hanchett
---
.changeset/cuddly-boxes-agree.md | 5 +++++
.changeset/pretty-ladybugs-taste.md | 5 +++++
2 files changed, 10 insertions(+)
create mode 100644 .changeset/cuddly-boxes-agree.md
create mode 100644 .changeset/pretty-ladybugs-taste.md
diff --git a/.changeset/cuddly-boxes-agree.md b/.changeset/cuddly-boxes-agree.md
new file mode 100644
index 0000000000..681881c007
--- /dev/null
+++ b/.changeset/cuddly-boxes-agree.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Update DocsTable and EntityListDocsTable to accept overrides for Material Table options.
diff --git a/.changeset/pretty-ladybugs-taste.md b/.changeset/pretty-ladybugs-taste.md
new file mode 100644
index 0000000000..5c87cde123
--- /dev/null
+++ b/.changeset/pretty-ladybugs-taste.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Adds new type, TableOptions, extending Material Table Options.
From 597eccb922735a624cb6d3b22a858c59e723098e Mon Sep 17 00:00:00 2001
From: ivgo
Date: Wed, 18 Jan 2023 14:20:31 +0100
Subject: [PATCH 013/156] Add s3-viewer plugin
Signed-off-by: ivgo
---
microsite/data/plugins/s3-viewer.yaml | 12 ++++++++++++
microsite/static/img/s3-bucket.svg | 1 +
2 files changed, 13 insertions(+)
create mode 100644 microsite/data/plugins/s3-viewer.yaml
create mode 100644 microsite/static/img/s3-bucket.svg
diff --git a/microsite/data/plugins/s3-viewer.yaml b/microsite/data/plugins/s3-viewer.yaml
new file mode 100644
index 0000000000..23637133c8
--- /dev/null
+++ b/microsite/data/plugins/s3-viewer.yaml
@@ -0,0 +1,12 @@
+---
+title: S3 Viewer
+author: Spreadgroup
+authorUrl: https://github.com/spreadshirt
+category: AWS
+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
+npmPackageName: '@spreadshirt/backstage-plugin-s3-viewer'
+tags:
+ - s3
+addedDate: '2023-01-18'
diff --git a/microsite/static/img/s3-bucket.svg b/microsite/static/img/s3-bucket.svg
new file mode 100644
index 0000000000..a92ac68982
--- /dev/null
+++ b/microsite/static/img/s3-bucket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
From 7323f98403a941849ce0b4fc65077a6586a6e50f Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Wed, 18 Jan 2023 09:25:37 -0700
Subject: [PATCH 014/156] change 2022 to 2020
Signed-off-by: Kurt King
---
packages/core-components/src/components/Link/Link.stories.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core-components/src/components/Link/Link.stories.tsx b/packages/core-components/src/components/Link/Link.stories.tsx
index 17e331b0b5..6049ec68b1 100644
--- a/packages/core-components/src/components/Link/Link.stories.tsx
+++ b/packages/core-components/src/components/Link/Link.stories.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Backstage Authors
+ * 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.
From 0b908d35fd958242e02cbfb21cecb24d56aec23e Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Wed, 18 Jan 2023 09:45:40 -0700
Subject: [PATCH 015/156] rename Button to LinkButton
Signed-off-by: Kurt King
---
.../src/components/Button/Button.stories.tsx | 170 ------------------
.../src/components/Button/Button.test.tsx | 44 -----
.../src/components/Button/Button.tsx | 51 ------
.../src/components/Button/index.ts | 17 --
.../src/components/LinkButton/LinkButton.tsx | 20 ++-
.../src/components/LinkButton/index.ts | 3 +
.../core-components/src/components/index.ts | 2 +-
.../layout/ErrorBoundary/ErrorBoundary.tsx | 2 +-
8 files changed, 18 insertions(+), 291 deletions(-)
delete mode 100644 packages/core-components/src/components/Button/Button.stories.tsx
delete mode 100644 packages/core-components/src/components/Button/Button.test.tsx
delete mode 100644 packages/core-components/src/components/Button/Button.tsx
delete mode 100644 packages/core-components/src/components/Button/index.ts
diff --git a/packages/core-components/src/components/Button/Button.stories.tsx b/packages/core-components/src/components/Button/Button.stories.tsx
deleted file mode 100644
index ca95316ee8..0000000000
--- a/packages/core-components/src/components/Button/Button.stories.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, { ComponentType } from 'react';
-import { Button } from './Button';
-import { useLocation } from 'react-router-dom';
-import { createRouteRef, useRouteRef } from '@backstage/core-plugin-api';
-import Divider from '@material-ui/core/Divider';
-import List from '@material-ui/core/List';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemText from '@material-ui/core/ListItemText';
-import Typography from '@material-ui/core/Typography';
-import MaterialButton from '@material-ui/core/Button';
-import { wrapInTestApp } from '@backstage/test-utils';
-import { Link } from '../Link';
-
-const routeRef = createRouteRef({
- id: 'storybook.test-route',
-});
-
-const Location = () => {
- const location = useLocation();
- return
Current location: {location.pathname}
;
-};
-
-export default {
- title: 'Inputs/Button',
- component: Button,
- decorators: [
- (Story: ComponentType<{}>) =>
- wrapInTestApp(
- <>
-
- A collection of buttons that should be used in the Backstage
- interface. These leverage the properties inherited from{' '}
-
- Material-UI Button
-
- , but include an opinionated set that align to the Backstage design.
-
-
-
-
-
-
-
-
-
-
- >,
- { mountedRoutes: { '/hello': routeRef } },
- ),
- ],
-};
-
-export const Default = () => {
- const link = useRouteRef(routeRef);
- // Design Permutations:
- // color = default | primary | secondary
- // variant = contained | outlined | text
- return (
-
-
-
- Default Button:
- This is the default button design which should be used in most cases.
-
-
color="primary" variant="contained"
-
-
-
-
-
-
- Secondary Button:
- Used for actions that cancel, skip, and in general perform negative
- functions, etc.
-
-
color="secondary" variant="contained"
-
-
-
-
-
-
- Tertiary Button:
- Used commonly in a ButtonGroup and when the button function itself is
- not a primary function on a page.
-
-
color="default" variant="outlined"
-
-
-
-
-
- );
-};
-
-export const ButtonLinks = () => {
- const link = useRouteRef(routeRef);
-
- const handleClick = () => {
- return 'Your click worked!';
- };
-
- return (
- <>
-
- {
- // TODO: Refactor to use new routing mechanisms
- }
-
-
- has props for both Material-UI's component as well as for
- react-router-dom's Route object.
-
-
-
-
- links to a statically defined route. In general, this should be
- avoided.
-
-
-
-
- View URL
-
- links to a defined URL using Material-UI's Button.
-
-
-
-
- Trigger Event
-
- triggers an onClick event using Material-UI's Button.
-
-
- >
- );
-};
diff --git a/packages/core-components/src/components/Button/Button.test.tsx b/packages/core-components/src/components/Button/Button.test.tsx
deleted file mode 100644
index c5942e3d78..0000000000
--- a/packages/core-components/src/components/Button/Button.test.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React from 'react';
-import { render, fireEvent, act } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
-import { Button } from './Button';
-import { Route, Routes } from 'react-router-dom';
-
-describe('', () => {
- it('navigates using react-router', async () => {
- const testString = 'This is test string';
- const buttonLabel = 'Navigate!';
- const { getByText } = render(
- wrapInTestApp(
- <>
-
-
- {testString}} />
-
- >,
- ),
- );
-
- expect(() => getByText(testString)).toThrow();
- await act(async () => {
- fireEvent.click(getByText(buttonLabel));
- });
- expect(getByText(testString)).toBeInTheDocument();
- });
-});
diff --git a/packages/core-components/src/components/Button/Button.tsx b/packages/core-components/src/components/Button/Button.tsx
deleted file mode 100644
index 771d94adb5..0000000000
--- a/packages/core-components/src/components/Button/Button.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import MaterialButton, {
- ButtonProps as MaterialButtonProps,
-} from '@material-ui/core/Button';
-import React from 'react';
-import { Link, LinkProps } from '../Link';
-
-/**
- * Properties for {@link Button}
- *
- * @public
- * @remarks
- * @deprecated use `LinkButtonProps` instead
- *
- * See {@link https://v4.mui.com/api/button/#props | Material-UI Button Props} for all properties
- */
-export type ButtonProps = MaterialButtonProps &
- Omit;
-
-/**
- * This wrapper is here to reset the color of the Link and make typescript happy.
- */
-const LinkWrapper = React.forwardRef((props, ref) => (
-
-));
-
-/**
- * Thin wrapper on top of material-ui's {@link https://v4.mui.com/components/buttons/ | Button} component
- *
- * @public
- * @remarks
- * @deprecated use `LinkButton` instead
- */
-export const Button = React.forwardRef((props, ref) => (
-
-)) as (props: ButtonProps) => JSX.Element;
diff --git a/packages/core-components/src/components/Button/index.ts b/packages/core-components/src/components/Button/index.ts
deleted file mode 100644
index b3dc40e6e7..0000000000
--- a/packages/core-components/src/components/Button/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-export { Button } from './Button';
-export type { ButtonProps } from './Button';
diff --git a/packages/core-components/src/components/LinkButton/LinkButton.tsx b/packages/core-components/src/components/LinkButton/LinkButton.tsx
index 60c218ba5f..bef4c96497 100644
--- a/packages/core-components/src/components/LinkButton/LinkButton.tsx
+++ b/packages/core-components/src/components/LinkButton/LinkButton.tsx
@@ -43,11 +43,17 @@ const LinkWrapper = React.forwardRef((props, ref) => (
*
* @public
* @remarks
- *
- * Makes the Button to utilize react-router
*/
-export const LinkButton = React.forwardRef(
- (props, ref) => (
-
- ),
-) as (props: LinkButtonProps) => JSX.Element;
+export const LinkButton = React.forwardRef((props, ref) => (
+
+)) as (props: ButtonProps) => JSX.Element;
+
+/**
+ * @deprecated use LinkButton instead
+ */
+export const Button = LinkButton;
+
+/**
+ * @deprecated use LinkButtonProps instead
+ */
+export type ButtonProps = LinkButtonProps;
diff --git a/packages/core-components/src/components/LinkButton/index.ts b/packages/core-components/src/components/LinkButton/index.ts
index 8ca699ecdd..848203c2dc 100644
--- a/packages/core-components/src/components/LinkButton/index.ts
+++ b/packages/core-components/src/components/LinkButton/index.ts
@@ -15,3 +15,6 @@
*/
export { LinkButton } from './LinkButton';
export type { LinkButtonProps } from './LinkButton';
+
+export { Button } from './LinkButton';
+export type { ButtonProps } from './LinkButton';
diff --git a/packages/core-components/src/components/index.ts b/packages/core-components/src/components/index.ts
index 218df9043d..0057847754 100644
--- a/packages/core-components/src/components/index.ts
+++ b/packages/core-components/src/components/index.ts
@@ -16,7 +16,7 @@
export * from './AlertDisplay';
export * from './Avatar';
-export * from './Button';
+export * from './LinkButton';
export * from './CodeSnippet';
export * from './CopyTextButton';
export * from './CreateButton';
diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx
index da430d2f88..9157b57fe2 100644
--- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx
+++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx
@@ -16,7 +16,7 @@
import Typography from '@material-ui/core/Typography';
import React, { ComponentClass, Component, ErrorInfo } from 'react';
-import { Button } from '../../components/Button';
+import { Button } from '../../components/LinkButton';
import { ErrorPanel } from '../../components/ErrorPanel';
type SlackChannel = {
From 7e0f40a60ddaa1cc483586df4fdbe6089c48537c Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Wed, 18 Jan 2023 09:48:02 -0700
Subject: [PATCH 016/156] change 2022 to 2020
Signed-off-by: Kurt King
---
packages/core-components/src/components/Link/Link.test.tsx | 2 +-
packages/core-components/src/components/Link/Link.tsx | 2 +-
packages/core-components/src/components/Link/index.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx
index 6365c00faf..3538b79052 100644
--- a/packages/core-components/src/components/Link/Link.test.tsx
+++ b/packages/core-components/src/components/Link/Link.test.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Backstage Authors
+ * 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.
diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx
index 29214fb69b..440fcb811f 100644
--- a/packages/core-components/src/components/Link/Link.tsx
+++ b/packages/core-components/src/components/Link/Link.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Backstage Authors
+ * 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.
diff --git a/packages/core-components/src/components/Link/index.ts b/packages/core-components/src/components/Link/index.ts
index d5bca4728f..2160508451 100644
--- a/packages/core-components/src/components/Link/index.ts
+++ b/packages/core-components/src/components/Link/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Backstage Authors
+ * 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.
From cd5a2ad7c765a1d8a12c6f5680d794974c9696db Mon Sep 17 00:00:00 2001
From: Kurt King
Date: Wed, 18 Jan 2023 10:35:24 -0700
Subject: [PATCH 017/156] create new api-report
Signed-off-by: Kurt King
---
packages/core-components/api-report.md | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md
index b92b3145b5..fb5442216d 100644
--- a/packages/core-components/api-report.md
+++ b/packages/core-components/api-report.md
@@ -129,11 +129,15 @@ export type BreadcrumbsStyledBoxClassKey = 'root';
// @public
export function BrokenImageIcon(props: IconComponentProps): JSX.Element;
-// @public @deprecated
+// Warning: (ae-missing-release-tag) "Button" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public @deprecated (undocumented)
export const Button: (props: ButtonProps) => JSX.Element;
-// @public @deprecated
-export type ButtonProps = ButtonProps_2 & Omit;
+// Warning: (ae-missing-release-tag) "ButtonProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public @deprecated (undocumented)
+export type ButtonProps = LinkButtonProps;
// @public (undocumented)
export type CardActionsTopRightClassKey = 'root';
@@ -630,7 +634,7 @@ export function LinearGauge(props: Props_11): JSX.Element | null;
export const Link: (props: LinkProps) => JSX.Element;
// @public
-export const LinkButton: (props: LinkButtonProps) => JSX.Element;
+export const LinkButton: (props: ButtonProps) => JSX.Element;
// @public
export type LinkButtonProps = ButtonProps_2 &
From bd939999a9ce7e6881ad9b7ba968aabb04358e22 Mon Sep 17 00:00:00 2001
From: Ryan Hanchett
Date: Wed, 18 Jan 2023 10:11:53 -0800
Subject: [PATCH 018/156] fix: generate new api reports
Signed-off-by: Ryan Hanchett
---
packages/core-components/api-report.md | 6 ++++++
plugins/techdocs/api-report.md | 3 +++
2 files changed, 9 insertions(+)
diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md
index 4cdc42ee79..19bdfced27 100644
--- a/packages/core-components/api-report.md
+++ b/packages/core-components/api-report.md
@@ -31,6 +31,7 @@ import MaterialBreadcrumbs from '@material-ui/core/Breadcrumbs';
import { MaterialTableProps } from '@material-table/core';
import { NavLinkProps } from 'react-router-dom';
import { Options } from 'react-markdown';
+import { Options as Options_2 } from '@material-table/core';
import { Overrides } from '@material-ui/core/styles/overrides';
import { ProfileInfo } from '@backstage/core-plugin-api';
import { ProfileInfoApi } from '@backstage/core-plugin-api';
@@ -1392,6 +1393,11 @@ export type TableFiltersClassKey = 'root' | 'value' | 'heder' | 'filters';
// @public (undocumented)
export type TableHeaderClassKey = 'header';
+// Warning: (ae-missing-release-tag) "TableOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export interface TableOptions extends Options_2 {}
+
// Warning: (ae-missing-release-tag) "TableProps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md
index d1c05f5b8e..d403f4582e 100644
--- a/plugins/techdocs/api-report.md
+++ b/plugins/techdocs/api-report.md
@@ -20,6 +20,7 @@ import { ReactNode } from 'react';
import { ResultHighlight } from '@backstage/plugin-search-common';
import { RouteRef } from '@backstage/core-plugin-api';
import { TableColumn } from '@backstage/core-components';
+import { TableOptions } from '@backstage/core-components';
import { TableProps } from '@backstage/core-components';
import { TechDocsEntityMetadata as TechDocsEntityMetadata_2 } from '@backstage/plugin-techdocs-react';
import { TechDocsMetadata as TechDocsMetadata_2 } from '@backstage/plugin-techdocs-react';
@@ -100,6 +101,7 @@ export type DocsTableProps = {
loading?: boolean | undefined;
columns?: TableColumn[];
actions?: TableProps['actions'];
+ options?: TableOptions;
};
// @public
@@ -159,6 +161,7 @@ export const EntityListDocsTable: {
export type EntityListDocsTableProps = {
columns?: TableColumn[];
actions?: TableProps['actions'];
+ options?: TableOptions;
};
// @public
From 51bc158294b057147b950c64c076b36ae93aa67b Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Thu, 19 Jan 2023 14:12:42 +0100
Subject: [PATCH 019/156] add project schema to tests
Signed-off-by: Marc Bruins
---
.../src/lib/azure.test.ts | 3 +++
.../processors/AzureDevOpsDiscoveryProcessor.test.ts | 12 ++++++++++++
2 files changed, 15 insertions(+)
diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
index ec6ef654b6..4a68603246 100644
--- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
+++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts
@@ -114,6 +114,9 @@ describe('azure', () => {
{
fileName: 'catalog-info.yaml',
path: '/catalog-info.yaml',
+ project: {
+ name: '*',
+ },
repository: {
name: 'backstage',
},
diff --git a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts
index 9000b177fa..38df737179 100644
--- a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts
+++ b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts
@@ -135,6 +135,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => {
{
fileName: 'catalog-info.yaml',
path: '/catalog-info.yaml',
+ project: {
+ name: '*',
+ },
repository: {
name: 'backstage',
},
@@ -142,6 +145,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => {
{
fileName: 'catalog-info.yaml',
path: '/src/catalog-info.yaml',
+ project: {
+ name: '*',
+ },
repository: {
name: 'ios-app',
},
@@ -191,6 +197,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => {
repository: {
name: 'backstage',
},
+ project: {
+ name: '*',
+ },
},
]);
const emitter = jest.fn();
@@ -229,6 +238,9 @@ describe('AzureDevOpsDiscoveryProcessor', () => {
repository: {
name: 'backstage',
},
+ project: {
+ name: '*',
+ },
},
]);
const emitter = jest.fn();
From 0c0cdef24fa8de8bb80adcbd18c4b182b1d6fe43 Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Thu, 19 Jan 2023 14:13:36 +0100
Subject: [PATCH 020/156] change to patch upgrade
Signed-off-by: Marc Bruins
---
.changeset/ninety-hats-serve.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/ninety-hats-serve.md b/.changeset/ninety-hats-serve.md
index 637d56d47e..9d9828f54c 100644
--- a/.changeset/ninety-hats-serve.md
+++ b/.changeset/ninety-hats-serve.md
@@ -1,5 +1,5 @@
---
-'@backstage/plugin-catalog-backend-module-azure': minor
+'@backstage/plugin-catalog-backend-module-azure': patch
---
This will add the ability to use Azure DevOps with multi project with a single value which is a new feature as previously this had to be done manually for each project that you wanted to add
From 40bceabf36399b0d8fe4507f0a33c22044767f70 Mon Sep 17 00:00:00 2001
From: Ryan Hanchett
Date: Thu, 19 Jan 2023 11:41:48 -0800
Subject: [PATCH 021/156] chore: convert changesets from patch to minor
Signed-off-by: Ryan Hanchett
---
.changeset/cuddly-boxes-agree.md | 2 +-
.changeset/pretty-ladybugs-taste.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.changeset/cuddly-boxes-agree.md b/.changeset/cuddly-boxes-agree.md
index 681881c007..83f027ecb5 100644
--- a/.changeset/cuddly-boxes-agree.md
+++ b/.changeset/cuddly-boxes-agree.md
@@ -1,5 +1,5 @@
---
-'@backstage/plugin-techdocs': patch
+'@backstage/plugin-techdocs': minor
---
Update DocsTable and EntityListDocsTable to accept overrides for Material Table options.
diff --git a/.changeset/pretty-ladybugs-taste.md b/.changeset/pretty-ladybugs-taste.md
index 5c87cde123..b4620f4c51 100644
--- a/.changeset/pretty-ladybugs-taste.md
+++ b/.changeset/pretty-ladybugs-taste.md
@@ -1,5 +1,5 @@
---
-'@backstage/core-components': patch
+'@backstage/core-components': minor
---
Adds new type, TableOptions, extending Material Table Options.
From 783eb151a238fcc75c6a9b1410418c999b17d3ae Mon Sep 17 00:00:00 2001
From: Sayak Mukhopadhyay
Date: Tue, 17 Jan 2023 20:11:05 +0530
Subject: [PATCH 022/156] fix: docs with wrong identifiers
Signed-off-by: Sayak Mukhopadhyay
---
.../software-templates/writing-custom-field-extensions.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md
index be00453795..3bbe4777fb 100644
--- a/docs/features/software-templates/writing-custom-field-extensions.md
+++ b/docs/features/software-templates/writing-custom-field-extensions.md
@@ -94,14 +94,14 @@ import {
createScaffolderFieldExtension,
} from '@backstage/plugin-scaffolder';
import {
- ValidateKebabCase,
+ ValidateKebabCaseExtension,
validateKebabCaseValidation,
} from './ValidateKebabCase';
export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'ValidateKebabCase',
- component: ValidateKebabCase,
+ component: ValidateKebabCaseExtension,
validation: validateKebabCaseValidation,
}),
);
@@ -173,7 +173,7 @@ spec:
title: Name
type: string
description: My custom name for the component
- ui:field: ValidateKebabCaseExtension
+ ui:field: ValidateKebabCase
steps:
[...]
```
From 0eebcb20c4f99c9dd3abacb553e40517947b03b8 Mon Sep 17 00:00:00 2001
From: Sayak Mukhopadhyay
Date: Fri, 20 Jan 2023 22:46:25 +0530
Subject: [PATCH 023/156] fix: field extensions with correct CSS and HTML
Signed-off-by: Sayak Mukhopadhyay
---
.../software-templates/writing-custom-field-extensions.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md
index 3bbe4777fb..20575a0b4a 100644
--- a/docs/features/software-templates/writing-custom-field-extensions.md
+++ b/docs/features/software-templates/writing-custom-field-extensions.md
@@ -35,7 +35,7 @@ import FormControl from '@material-ui/core/FormControl';
/*
This is the actual component that will get rendered in the form
*/
-export const ValidateKebabCaseExtension = ({
+export const ValidateKebabCase = ({
onChange,
rawErrors,
required,
@@ -94,14 +94,14 @@ import {
createScaffolderFieldExtension,
} from '@backstage/plugin-scaffolder';
import {
- ValidateKebabCaseExtension,
+ ValidateKebabCase,
validateKebabCaseValidation,
-} from './ValidateKebabCase';
+} from './ValidateKebabCase/ValidateKebabCaseExtension';
export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'ValidateKebabCase',
- component: ValidateKebabCaseExtension,
+ component: ValidateKebabCase,
validation: validateKebabCaseValidation,
}),
);
From ac15c0cb1f316a86eb2143fff27e2cffdd4ef025 Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Sun, 22 Jan 2023 19:39:59 +0100
Subject: [PATCH 024/156] implement gitlab org catalog provider
Signed-off-by: Dominik Pfaffenbauer
---
docs/integrations/gitlab/org.md | 29 +
.../src/index.ts | 5 +-
.../src/lib/client.ts | 36 ++
.../src/lib/types.ts | 29 +
.../GitlabOrgDiscoveryEntityProvider.test.ts | 514 ++++++++++++++++++
.../GitlabOrgDiscoveryEntityProvider.ts | 363 +++++++++++++
.../src/providers/config.test.ts | 9 +
.../src/providers/config.ts | 10 +
.../src/providers/index.ts | 1 +
9 files changed, 995 insertions(+), 1 deletion(-)
create mode 100644 docs/integrations/gitlab/org.md
create mode 100644 plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts
create mode 100644 plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md
new file mode 100644
index 0000000000..caf0851477
--- /dev/null
+++ b/docs/integrations/gitlab/org.md
@@ -0,0 +1,29 @@
+---
+id: org
+title: GitLab Org
+sidebar_label: Org Data
+description: Importing users and groups from a GitLab organization into Backstage
+---
+
+The Backstage catalog can be set up to ingest organizational data - users and
+teams - directly from an organization in GitLab. The result
+is a hierarchy of
+[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and
+[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
+entities that mirror your org setup.
+
+```yaml
+integrations:
+ gitlab:
+ - host: gitlab.com
+ token: ${GITLAB_TOKEN}
+```
+
+```yaml
+catalog:
+ providers:
+ gitlab:
+ yourProviderId:
+ host: gitlab.com
+ orgEnabled: true
+```
diff --git a/plugins/catalog-backend-module-gitlab/src/index.ts b/plugins/catalog-backend-module-gitlab/src/index.ts
index 27aac9e78f..30efac4924 100644
--- a/plugins/catalog-backend-module-gitlab/src/index.ts
+++ b/plugins/catalog-backend-module-gitlab/src/index.ts
@@ -21,5 +21,8 @@
*/
export { GitLabDiscoveryProcessor } from './GitLabDiscoveryProcessor';
-export { GitlabDiscoveryEntityProvider } from './providers';
+export {
+ GitlabDiscoveryEntityProvider,
+ GitlabOrgDiscoveryEntityProvider,
+} from './providers';
export { gitlabDiscoveryEntityProviderCatalogModule } from './service/GitlabDiscoveryEntityProviderCatalogModule';
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
index adf0a78e8f..b86de7bf1d 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
@@ -20,12 +20,14 @@ import {
GitLabIntegrationConfig,
} from '@backstage/integration';
import { Logger } from 'winston';
+import { GitLabGroup, GitLabMembership, GitLabUser } from './types';
export type ListOptions = {
[key: string]: string | number | boolean | undefined;
group?: string;
per_page?: number | undefined;
page?: number | undefined;
+ active?: boolean;
};
export type PagedResponse = {
@@ -63,6 +65,40 @@ export class GitLabClient {
return this.pagedRequest(`/projects`, options);
}
+ async listUsers(options?: ListOptions): Promise> {
+ return this.pagedRequest(`/users`, options);
+ }
+
+ async listGroups(options?: ListOptions): Promise> {
+ return this.pagedRequest(`/groups`, options);
+ }
+
+ async getUserMemberships(userId: number): Promise {
+ const endpoint: string = `/users/${encodeURIComponent(userId)}/memberships`;
+ const request = new URL(`${this.config.apiBaseUrl}${endpoint}`);
+ request.searchParams.append('per_page', '100');
+
+ const response = await fetch(request.toString(), {
+ headers: getGitLabRequestOptions(this.config).headers,
+ method: 'GET',
+ });
+
+ if (!response.ok) {
+ if (response.status >= 500) {
+ this.logger.debug(
+ `Unexpected response when fetching ${request.toString()}. Expected 200 but got ${
+ response.status
+ } - ${response.statusText}`,
+ );
+ }
+ return [];
+ }
+
+ return response.json().then(items => {
+ return items as GitLabMembership[];
+ });
+ }
+
/**
* General existence check.
*
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts
index 86fe1aa385..97ea87ea98 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts
@@ -31,6 +31,32 @@ export type GitLabProject = {
path_with_namespace?: string;
};
+export type GitLabUser = {
+ id: number;
+ username: string;
+ name: string;
+ email: string;
+ active: boolean;
+ web_url: string;
+ avatar_url: string;
+ groups?: GitLabGroup[];
+};
+
+export type GitLabGroup = {
+ id: number;
+ name: string;
+ full_path: string;
+ description?: string;
+ parent_id?: number;
+};
+
+export type GitLabMembership = {
+ source_id: number;
+ source_name: string;
+ source_type: string;
+ access_level: number;
+};
+
export type GitlabProviderConfig = {
host: string;
group: string;
@@ -38,5 +64,8 @@ export type GitlabProviderConfig = {
branch: string;
catalogFile: string;
projectPattern: RegExp;
+ userPattern: RegExp;
+ groupPattern: RegExp;
+ orgEnabled?: boolean;
schedule?: TaskScheduleDefinition;
};
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts
new file mode 100644
index 0000000000..74649f41da
--- /dev/null
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts
@@ -0,0 +1,514 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { getVoidLogger } from '@backstage/backend-common';
+import {
+ PluginTaskScheduler,
+ TaskInvocationDefinition,
+ TaskRunner,
+} from '@backstage/backend-tasks';
+import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
+import { ConfigReader } from '@backstage/config';
+import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+import { GitlabOrgDiscoveryEntityProvider } from './GitlabOrgDiscoveryEntityProvider';
+
+class PersistingTaskRunner implements TaskRunner {
+ private tasks: TaskInvocationDefinition[] = [];
+
+ getTasks() {
+ return this.tasks;
+ }
+
+ run(task: TaskInvocationDefinition): Promise {
+ this.tasks.push(task);
+ return Promise.resolve(undefined);
+ }
+}
+
+const logger = getVoidLogger();
+
+const server = setupServer();
+
+describe('GitlabOrgDiscoveryEntityProvider', () => {
+ setupRequestMockHandlers(server);
+ afterEach(() => jest.resetAllMocks());
+
+ it('no provider config', () => {
+ const schedule = new PersistingTaskRunner();
+ const config = new ConfigReader({});
+ const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ schedule,
+ });
+
+ expect(providers).toHaveLength(0);
+ });
+
+ it('single simple discovery config with org disabled', () => {
+ const schedule = new PersistingTaskRunner();
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ },
+ },
+ },
+ },
+ });
+ const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ schedule,
+ });
+
+ expect(providers).toHaveLength(0);
+ });
+
+ it('single simple discovery config with org enabled', () => {
+ const schedule = new PersistingTaskRunner();
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ orgEnabled: true,
+ },
+ },
+ },
+ },
+ });
+ const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ schedule,
+ });
+
+ expect(providers).toHaveLength(1);
+ expect(providers[0].getProviderName()).toEqual(
+ 'GitlabOrgDiscoveryEntityProvider:test-id',
+ );
+ });
+
+ it('multiple discovery configs', () => {
+ const schedule = new PersistingTaskRunner();
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ orgEnabled: true,
+ },
+ 'second-test': {
+ host: 'test-gitlab',
+ group: 'second-group',
+ orgEnabled: true,
+ },
+ },
+ },
+ },
+ });
+ const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ schedule,
+ });
+
+ expect(providers).toHaveLength(2);
+ expect(providers[0].getProviderName()).toEqual(
+ 'GitlabOrgDiscoveryEntityProvider:test-id',
+ );
+ expect(providers[1].getProviderName()).toEqual(
+ 'GitlabOrgDiscoveryEntityProvider:second-test',
+ );
+ });
+
+ it('apply full update on scheduled execution', async () => {
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ orgEnabled: true,
+ },
+ },
+ },
+ },
+ });
+ const schedule = new PersistingTaskRunner();
+ const entityProviderConnection: EntityProviderConnection = {
+ applyMutation: jest.fn(),
+ refresh: jest.fn(),
+ };
+ const provider = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ schedule,
+ })[0];
+ expect(provider.getProviderName()).toEqual(
+ 'GitlabOrgDiscoveryEntityProvider:test-id',
+ );
+
+ server.use(
+ rest.get(
+ `https://api.gitlab.example/api/v4/groups/test-group/projects`,
+ (_req, res, ctx) => {
+ const response = [
+ {
+ id: 123,
+ default_branch: 'master',
+ archived: false,
+ last_activity_at: new Date().toString(),
+ web_url: 'https://api.gitlab.example/test-group/test-repo',
+ path_with_namespace: 'test-group/test-repo',
+ },
+ ];
+ return res(ctx.json(response));
+ },
+ ),
+ rest.get(`https://api.gitlab.example/api/v4/users`, (_req, res, ctx) => {
+ const response = [
+ {
+ id: 1,
+ username: 'test1',
+ name: 'Test Testit',
+ state: 'active',
+ avatar_url: 'https://secure.gravatar.com/',
+ web_url: 'https://gitlab.example/test1',
+ created_at: '2023-01-19T07:27:03.333Z',
+ bio: '',
+ location: null,
+ public_email: null,
+ skype: '',
+ linkedin: '',
+ twitter: '',
+ website_url: '',
+ organization: null,
+ job_title: '',
+ pronouns: null,
+ bot: false,
+ work_information: null,
+ followers: 0,
+ following: 0,
+ is_followed: false,
+ local_time: null,
+ last_sign_in_at: '2023-01-19T07:27:49.601Z',
+ confirmed_at: '2023-01-19T07:27:02.905Z',
+ last_activity_on: '2023-01-19',
+ email: 'test@example.com',
+ theme_id: 1,
+ color_scheme_id: 1,
+ projects_limit: 100000,
+ current_sign_in_at: '2023-01-19T09:09:10.676Z',
+ identities: [],
+ can_create_group: true,
+ can_create_project: true,
+ two_factor_enabled: false,
+ external: false,
+ private_profile: false,
+ commit_email: 'test@example.com',
+ is_admin: false,
+ note: '',
+ },
+ ];
+ return res(ctx.json(response));
+ }),
+ rest.get(`https://api.gitlab.example/api/v4/groups`, (_req, res, ctx) => {
+ const response = [
+ {
+ id: 1,
+ web_url: 'https://gitlab.example/groups/group1',
+ name: 'group1',
+ path: 'group1',
+ description: '',
+ visibility: 'internal',
+ share_with_group_lock: false,
+ require_two_factor_authentication: false,
+ two_factor_grace_period: 48,
+ project_creation_level: 'developer',
+ auto_devops_enabled: null,
+ subgroup_creation_level: 'owner',
+ emails_disabled: null,
+ mentions_disabled: null,
+ lfs_enabled: true,
+ default_branch_protection: 2,
+ avatar_url: null,
+ request_access_enabled: false,
+ full_name: '8020',
+ full_path: '8020',
+ created_at: '2017-06-19T06:42:34.160Z',
+ parent_id: null,
+ },
+ {
+ id: 2,
+ web_url: 'https://gitlab.example/groups/group1/group2',
+ name: 'group2',
+ path: 'group1/group2',
+ description: 'Group2',
+ visibility: 'internal',
+ share_with_group_lock: false,
+ require_two_factor_authentication: false,
+ two_factor_grace_period: 48,
+ project_creation_level: 'developer',
+ auto_devops_enabled: null,
+ subgroup_creation_level: 'owner',
+ emails_disabled: null,
+ mentions_disabled: null,
+ lfs_enabled: true,
+ request_access_enabled: false,
+ full_name: 'group2',
+ full_path: 'group1/group2',
+ created_at: '2017-12-07T13:20:40.675Z',
+ parent_id: null,
+ },
+ ];
+ return res(ctx.json(response));
+ }),
+ rest.get(
+ `https://api.gitlab.example/api/v4/users/1/memberships`,
+ (_req, res, ctx) => {
+ const response = [
+ {
+ source_id: 2,
+ source_name: 'Group 2',
+ source_type: 'Namespace',
+ access_level: 50,
+ },
+ ];
+ return res(ctx.json(response));
+ },
+ ),
+ );
+
+ await provider.connect(entityProviderConnection);
+
+ const taskDef = schedule.getTasks()[0];
+ expect(taskDef.id).toEqual(
+ 'GitlabOrgDiscoveryEntityProvider:test-id:refresh',
+ );
+ await (taskDef.fn as () => Promise)();
+
+ const expectedEntities = [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location': 'url:test-gitlab/test1',
+ 'backstage.io/managed-by-origin-location':
+ 'url:test-gitlab/test1',
+ 'test-gitlab/user-login': 'https://gitlab.example/test1',
+ },
+ name: 'test1',
+ },
+ spec: {
+ memberOf: ['group1-group2'],
+ profile: {
+ displayName: 'Test Testit',
+ email: 'test@example.com',
+ picture: 'https://secure.gravatar.com/',
+ },
+ },
+ },
+ locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
+ },
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:test-gitlab/teams/group1-group2',
+ 'backstage.io/managed-by-origin-location':
+ 'url:test-gitlab/teams/group1-group2',
+ 'test-gitlab/team-path': 'group1/group2',
+ },
+ description: 'Group2',
+ name: 'group1-group2',
+ },
+ spec: {
+ children: [],
+ profile: {
+ displayName: 'group2',
+ },
+ type: 'team',
+ },
+ },
+ locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
+ },
+ ];
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'full',
+ entities: expectedEntities,
+ });
+ });
+
+ it('fail without schedule and scheduler', () => {
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ orgEnabled: true,
+ },
+ },
+ },
+ },
+ });
+
+ expect(() =>
+ GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ }),
+ ).toThrow('Either schedule or scheduler must be provided');
+ });
+
+ it('fail with scheduler but no schedule config', () => {
+ const scheduler = {
+ createScheduledTaskRunner: (_: any) => jest.fn(),
+ } as unknown as PluginTaskScheduler;
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ orgEnabled: true,
+ },
+ },
+ },
+ },
+ });
+
+ expect(() =>
+ GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ scheduler,
+ }),
+ ).toThrow(
+ 'No schedule provided neither via code nor config for GitlabOrgDiscoveryEntityProvider:test-id',
+ );
+ });
+
+ it('single simple provider config with schedule in config', async () => {
+ const schedule = new PersistingTaskRunner();
+ const scheduler = {
+ createScheduledTaskRunner: (_: any) => schedule,
+ } as unknown as PluginTaskScheduler;
+ const config = new ConfigReader({
+ integrations: {
+ gitlab: [
+ {
+ host: 'test-gitlab',
+ apiBaseUrl: 'https://api.gitlab.example/api/v4',
+ token: '1234',
+ },
+ ],
+ },
+ catalog: {
+ providers: {
+ gitlab: {
+ 'test-id': {
+ host: 'test-gitlab',
+ group: 'test-group',
+ orgEnabled: true,
+ schedule: {
+ frequency: 'PT30M',
+ timeout: 'PT3M',
+ },
+ },
+ },
+ },
+ },
+ });
+ const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
+ logger,
+ scheduler,
+ });
+
+ expect(providers).toHaveLength(1);
+ expect(providers[0].getProviderName()).toEqual(
+ 'GitlabOrgDiscoveryEntityProvider:test-id',
+ );
+ });
+});
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
new file mode 100644
index 0000000000..ca4f9ec719
--- /dev/null
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2021 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 { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks';
+import { Config } from '@backstage/config';
+import { GitLabIntegration, ScmIntegrations } from '@backstage/integration';
+import {
+ EntityProvider,
+ EntityProviderConnection,
+} from '@backstage/plugin-catalog-backend';
+import * as uuid from 'uuid';
+import { Logger } from 'winston';
+import {
+ GitLabClient,
+ GitlabProviderConfig,
+ paginated,
+ readGitlabConfigs,
+} from '../lib';
+import { GitLabGroup, GitLabUser } from '../lib/types';
+import {
+ ANNOTATION_LOCATION,
+ ANNOTATION_ORIGIN_LOCATION,
+ Entity,
+ UserEntity,
+ GroupEntity,
+} from '@backstage/catalog-model';
+import { merge } from 'lodash';
+
+type Result = {
+ scanned: number;
+ matches: GitLabUser[];
+};
+
+type GroupResult = {
+ scanned: number;
+ matches: GitLabGroup[];
+};
+
+/**
+ * Discovers entity definition files in the groups of a Gitlab instance.
+ * @public
+ */
+export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
+ private readonly config: GitlabProviderConfig;
+ private readonly integration: GitLabIntegration;
+ private readonly logger: Logger;
+ private readonly scheduleFn: () => Promise;
+ private connection?: EntityProviderConnection;
+
+ static fromConfig(
+ config: Config,
+ options: {
+ logger: Logger;
+ schedule?: TaskRunner;
+ scheduler?: PluginTaskScheduler;
+ },
+ ): GitlabOrgDiscoveryEntityProvider[] {
+ if (!options.schedule && !options.scheduler) {
+ throw new Error('Either schedule or scheduler must be provided.');
+ }
+
+ const providerConfigs = readGitlabConfigs(config);
+ const integrations = ScmIntegrations.fromConfig(config).gitlab;
+ const providers: GitlabOrgDiscoveryEntityProvider[] = [];
+
+ providerConfigs.forEach(providerConfig => {
+ const integration = integrations.byHost(providerConfig.host);
+ if (!integration) {
+ throw new Error(
+ `No gitlab integration found that matches host ${providerConfig.host}`,
+ );
+ }
+
+ if (!options.schedule && !providerConfig.schedule) {
+ throw new Error(
+ `No schedule provided neither via code nor config for GitlabOrgDiscoveryEntityProvider:${providerConfig.id}.`,
+ );
+ }
+
+ if (!providerConfig.orgEnabled) {
+ return;
+ }
+
+ const taskRunner =
+ options.schedule ??
+ options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!);
+
+ providers.push(
+ new GitlabOrgDiscoveryEntityProvider({
+ ...options,
+ config: providerConfig,
+ integration,
+ taskRunner,
+ }),
+ );
+ });
+ return providers;
+ }
+
+ private constructor(options: {
+ config: GitlabProviderConfig;
+ integration: GitLabIntegration;
+ logger: Logger;
+ taskRunner: TaskRunner;
+ }) {
+ this.config = options.config;
+ this.integration = options.integration;
+ this.logger = options.logger.child({
+ target: this.getProviderName(),
+ });
+ this.scheduleFn = this.createScheduleFn(options.taskRunner);
+ }
+
+ getProviderName(): string {
+ return `GitlabOrgDiscoveryEntityProvider:${this.config.id}`;
+ }
+
+ async connect(connection: EntityProviderConnection): Promise {
+ this.connection = connection;
+ await this.scheduleFn();
+ }
+
+ private createScheduleFn(taskRunner: TaskRunner): () => Promise {
+ return async () => {
+ const taskId = `${this.getProviderName()}:refresh`;
+ return taskRunner.run({
+ id: taskId,
+ fn: async () => {
+ const logger = this.logger.child({
+ class: GitlabOrgDiscoveryEntityProvider.prototype.constructor.name,
+ taskId,
+ taskInstanceId: uuid.v4(),
+ });
+
+ try {
+ await this.refresh(logger);
+ } catch (error) {
+ logger.error(`${this.getProviderName()} refresh failed`, error);
+ }
+ },
+ });
+ };
+ }
+
+ async refresh(logger: Logger): Promise {
+ if (!this.connection) {
+ throw new Error(
+ `Gitlab discovery connection not initialized for ${this.getProviderName()}`,
+ );
+ }
+
+ const client = new GitLabClient({
+ config: this.integration.config,
+ logger: logger,
+ });
+
+ const users = paginated(options => client.listUsers(options), {
+ page: 1,
+ per_page: 50,
+ active: true,
+ });
+
+ const groups = paginated(
+ options => client.listGroups(options),
+ {
+ page: 1,
+ per_page: 50,
+ },
+ );
+
+ const idMappedGroup: { [groupId: number]: GitLabGroup } = {};
+
+ const res: Result = {
+ scanned: 0,
+ matches: [],
+ };
+
+ const groupRes: GroupResult = {
+ scanned: 0,
+ matches: [],
+ };
+
+ for await (const group of groups) {
+ if (!this.config.groupPattern.test(group.full_path ?? '')) {
+ continue;
+ }
+
+ groupRes.scanned++;
+ groupRes.matches.push(group);
+
+ idMappedGroup[group.id] = group;
+ }
+
+ for await (const user of users) {
+ if (!this.config.userPattern.test(user.email ?? '')) {
+ continue;
+ }
+
+ res.scanned++;
+
+ if (user.active) {
+ continue;
+ }
+
+ const memberships = await client.getUserMemberships(user.id);
+ const userGroups: GitLabGroup[] = [];
+
+ for (const i of memberships) {
+ if (
+ i.source_type === 'Namespace' &&
+ idMappedGroup.hasOwnProperty(i.source_id)
+ ) {
+ userGroups.push(idMappedGroup[i.source_id]);
+ }
+ }
+
+ user.groups = userGroups;
+
+ res.matches.push(user);
+ }
+
+ const groupsWithUsers = groupRes.matches.filter(group => {
+ return (
+ res.matches.filter(x => {
+ return !!x.groups?.find(y => y.id === group.id);
+ }).length > 0
+ );
+ });
+
+ const userEntities = res.matches.map(p =>
+ this.createUserEntity(p, this.integration.config.host),
+ );
+ const groupEntities = this.createGroupEntities(
+ groupsWithUsers,
+ this.integration.config.host,
+ );
+
+ await this.connection.applyMutation({
+ type: 'full',
+ entities: [...userEntities, ...groupEntities].map(entity => ({
+ locationKey: this.getProviderName(),
+ entity: this.withLocations(this.integration.config.host, entity),
+ })),
+ });
+ }
+
+ private createGroupEntities(
+ groupResult: GitLabGroup[],
+ host: string,
+ ): GroupEntity[] {
+ const idMapped: { [groupId: number]: GitLabGroup } = {};
+ const entities: GroupEntity[] = [];
+
+ for (const group of groupResult) {
+ idMapped[group.id] = group;
+ }
+
+ for (const group of groupResult) {
+ const entity = this.createGroupEntity(group, host);
+
+ if (group.parent_id && idMapped.hasOwnProperty(group.parent_id)) {
+ entity.spec.parent = idMapped[group.parent_id].full_path;
+ }
+
+ entities.push(entity);
+ }
+
+ return entities;
+ }
+
+ private withLocations(host: string, entity: Entity): Entity {
+ const location =
+ entity.kind === 'Group'
+ ? `url:${host}/teams/${entity.metadata.name}`
+ : `url:${host}/${entity.metadata.name}`;
+ return merge(
+ {
+ metadata: {
+ annotations: {
+ [ANNOTATION_LOCATION]: location,
+ [ANNOTATION_ORIGIN_LOCATION]: location,
+ },
+ },
+ },
+ entity,
+ ) as Entity;
+ }
+
+ private createUserEntity(user: GitLabUser, host: string): UserEntity {
+ const annotations: { [annotationName: string]: string } = {};
+
+ annotations[`${host}/user-login`] = user.web_url;
+
+ const entity: UserEntity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ name: user.username,
+ annotations: annotations,
+ },
+ spec: {
+ profile: {
+ email: user.email,
+ displayName: user.name,
+ picture: user.avatar_url,
+ },
+ memberOf: [],
+ },
+ };
+
+ if (user.groups) {
+ for (const group of user.groups) {
+ if (!entity.spec.memberOf) {
+ entity.spec.memberOf = [];
+ }
+ entity.spec.memberOf.push(group.full_path.replace('/', '-'));
+ }
+ }
+
+ return entity;
+ }
+
+ private createGroupEntity(group: GitLabGroup, host: string): GroupEntity {
+ const annotations: { [annotationName: string]: string } = {};
+
+ annotations[`${host}/team-path`] = group.full_path;
+
+ const entity: GroupEntity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ name: group.full_path.replace('/', '-'),
+ annotations: annotations,
+ },
+ spec: {
+ type: 'team',
+ children: [],
+ profile: {
+ displayName: group.name,
+ },
+ },
+ };
+
+ if (group.description) {
+ entity.metadata.description = group.description;
+ }
+
+ return entity;
+ }
+}
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts
index 6b337bf7cd..e72c38e9d1 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts
@@ -54,6 +54,9 @@ describe('config', () => {
host: 'host',
catalogFile: 'catalog-info.yaml',
projectPattern: /[\s\S]*/,
+ groupPattern: /[\s\S]*/,
+ userPattern: /[\s\S]*/,
+ orgEnabled: false,
schedule: undefined,
}),
);
@@ -85,6 +88,9 @@ describe('config', () => {
host: 'host',
catalogFile: 'custom-file.yaml',
projectPattern: /[\s\S]*/,
+ groupPattern: /[\s\S]*/,
+ userPattern: /[\s\S]*/,
+ orgEnabled: false,
schedule: undefined,
}),
);
@@ -120,6 +126,9 @@ describe('config', () => {
host: 'host',
catalogFile: 'catalog-info.yaml',
projectPattern: /[\s\S]*/,
+ groupPattern: /[\s\S]*/,
+ userPattern: /[\s\S]*/,
+ orgEnabled: false,
schedule: {
frequency: Duration.fromISO('PT30M'),
timeout: {
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts
index 3dfc00745e..9980d59023 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts
@@ -35,6 +35,13 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
const projectPattern = new RegExp(
config.getOptionalString('projectPattern') ?? /[\s\S]*/,
);
+ const userPattern = new RegExp(
+ config.getOptionalString('userPattern') ?? /[\s\S]*/,
+ );
+ const groupPattern = new RegExp(
+ config.getOptionalString('grupPattern') ?? /[\s\S]*/,
+ );
+ const orgEnabled: boolean = config.getOptionalBoolean('orgEnabled') ?? false;
const schedule = config.has('schedule')
? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule'))
@@ -47,7 +54,10 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
host,
catalogFile,
projectPattern,
+ userPattern,
+ groupPattern,
schedule,
+ orgEnabled,
};
}
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/index.ts b/plugins/catalog-backend-module-gitlab/src/providers/index.ts
index e7cb00a73f..2091b3f47a 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/index.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/index.ts
@@ -15,3 +15,4 @@
*/
export { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider';
+export { GitlabOrgDiscoveryEntityProvider } from './GitlabOrgDiscoveryEntityProvider';
From 52c5685ceb01a355d959a72cebb7496d1730a181 Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Sun, 22 Jan 2023 19:40:56 +0100
Subject: [PATCH 025/156] implement gitlab org catalog provider
Signed-off-by: Dominik Pfaffenbauer
---
.changeset/soft-boxes-buy.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/soft-boxes-buy.md
diff --git a/.changeset/soft-boxes-buy.md b/.changeset/soft-boxes-buy.md
new file mode 100644
index 0000000000..5e50be4028
--- /dev/null
+++ b/.changeset/soft-boxes-buy.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-gitlab': minor
+---
+
+Implement Group and User Catalog Provider
From 2318e7956d076156b19e0b4924a7a8ee25ff4e72 Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Mon, 23 Jan 2023 08:12:56 +0100
Subject: [PATCH 026/156] api-report
Signed-off-by: Dominik Pfaffenbauer
---
.../api-report.md | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md
index 36713d2427..b69b9904f5 100644
--- a/plugins/catalog-backend-module-gitlab/api-report.md
+++ b/plugins/catalog-backend-module-gitlab/api-report.md
@@ -55,4 +55,23 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor {
emit: CatalogProcessorEmit,
): Promise;
}
+
+// @public
+export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
+ // (undocumented)
+ connect(connection: EntityProviderConnection): Promise;
+ // (undocumented)
+ static fromConfig(
+ config: Config,
+ options: {
+ logger: Logger;
+ schedule?: TaskRunner;
+ scheduler?: PluginTaskScheduler;
+ },
+ ): GitlabOrgDiscoveryEntityProvider[];
+ // (undocumented)
+ getProviderName(): string;
+ // (undocumented)
+ refresh(logger: Logger): Promise;
+}
```
From 2d60f01e2a7bff2d0d9813279b7664c63f932645 Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Mon, 23 Jan 2023 09:36:21 +0100
Subject: [PATCH 027/156] fix typo
Signed-off-by: Dominik Pfaffenbauer
---
plugins/catalog-backend-module-gitlab/src/providers/config.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts
index 9980d59023..cd856f97da 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts
@@ -39,7 +39,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
config.getOptionalString('userPattern') ?? /[\s\S]*/,
);
const groupPattern = new RegExp(
- config.getOptionalString('grupPattern') ?? /[\s\S]*/,
+ config.getOptionalString('groupPattern') ?? /[\s\S]*/,
);
const orgEnabled: boolean = config.getOptionalBoolean('orgEnabled') ?? false;
From 42ac2ad34ce49a50f589a6d916ac40b255b1d76e Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Mon, 23 Jan 2023 12:22:38 +0100
Subject: [PATCH 028/156] move enabled check before schedule check
Signed-off-by: Dominik Pfaffenbauer
---
.../src/providers/GitlabOrgDiscoveryEntityProvider.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
index ca4f9ec719..3b862b6aaa 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
@@ -78,6 +78,11 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
providerConfigs.forEach(providerConfig => {
const integration = integrations.byHost(providerConfig.host);
+
+ if (!providerConfig.orgEnabled) {
+ return;
+ }
+
if (!integration) {
throw new Error(
`No gitlab integration found that matches host ${providerConfig.host}`,
@@ -90,10 +95,6 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
);
}
- if (!providerConfig.orgEnabled) {
- return;
- }
-
const taskRunner =
options.schedule ??
options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!);
From 85b04f659af088034fcdc80e49cc6e5185d44254 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Mon, 23 Jan 2023 14:20:17 +0100
Subject: [PATCH 029/156] get rid of usages of substr which is deprecated
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
.changeset/shy-steaks-invite.md | 16 ++++++++++++++++
microsite/pages/en/on-demand.js | 2 +-
microsite/pages/en/plugins.js | 2 +-
.../src/components/Avatar/utils.ts | 4 ++--
.../ScheduleIntervalLabel.tsx | 2 +-
.../src/api/AzureDevOpsApi.ts | 4 ++--
.../BitriseBuildsTableComponent.tsx | 2 +-
.../processors/AzureDevOpsDiscoveryProcessor.ts | 2 +-
.../src/BitbucketDiscoveryProcessor.ts | 4 ++--
.../src/lib/util.ts | 2 +-
.../src/processors/GithubDiscoveryProcessor.ts | 2 +-
.../src/providers/GithubEntityProvider.ts | 2 +-
.../src/GitLabDiscoveryProcessor.ts | 2 +-
plugins/circleci/src/state/useBuilds.ts | 2 +-
.../src/helpers/getShortCommitHash.ts | 2 +-
.../src/service/ListPlaylistsFilter.ts | 4 ++--
.../src/database/util.test.ts | 2 +-
.../src/components/ErrorCell/ErrorCell.tsx | 2 +-
18 files changed, 37 insertions(+), 21 deletions(-)
create mode 100644 .changeset/shy-steaks-invite.md
diff --git a/.changeset/shy-steaks-invite.md b/.changeset/shy-steaks-invite.md
new file mode 100644
index 0000000000..b4a70589f9
--- /dev/null
+++ b/.changeset/shy-steaks-invite.md
@@ -0,0 +1,16 @@
+---
+'@backstage/plugin-catalog-backend-module-bitbucket': patch
+'@backstage/plugin-catalog-backend-module-github': patch
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+'@backstage/plugin-catalog-backend-module-azure': patch
+'@backstage/plugin-azure-devops-backend': patch
+'@backstage/plugin-git-release-manager': patch
+'@backstage/core-components': patch
+'@backstage/plugin-playlist-backend': patch
+'@backstage/plugin-apache-airflow': patch
+'@backstage/plugin-circleci': patch
+'@backstage/plugin-bitrise': patch
+'@backstage/plugin-sentry': patch
+---
+
+Internal refactor to not use deprecated `substr`
diff --git a/microsite/pages/en/on-demand.js b/microsite/pages/en/on-demand.js
index 41a730205b..15059e91a1 100644
--- a/microsite/pages/en/on-demand.js
+++ b/microsite/pages/en/on-demand.js
@@ -20,7 +20,7 @@ const ondemandMetadata = fs
.reverse()
.map(file => yaml.load(fs.readFileSync(`./data/on-demand/${file}`, 'utf8')));
const truncate = text =>
- text.length > 170 ? text.substr(0, 170) + '...' : text;
+ text.length > 170 ? text.slice(0, 170) + '...' : text;
const addVideoDocsLink = '/docs/overview/support';
const defaultIconUrl = 'img/logo-gradient-on-dark.svg';
diff --git a/microsite/pages/en/plugins.js b/microsite/pages/en/plugins.js
index 9a0a59bd4b..237161b98a 100644
--- a/microsite/pages/en/plugins.js
+++ b/microsite/pages/en/plugins.js
@@ -19,7 +19,7 @@ const pluginMetadata = fs
.map(file => yaml.load(fs.readFileSync(`./data/plugins/${file}`, 'utf8')))
.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()));
const truncate = text =>
- text.length > 170 ? text.substr(0, 170) + '...' : text;
+ text.length > 170 ? text.slice(0, 170) + '...' : text;
const newForDays = 100;
diff --git a/packages/core-components/src/components/Avatar/utils.ts b/packages/core-components/src/components/Avatar/utils.ts
index d8310f18c0..4f71d736f1 100644
--- a/packages/core-components/src/components/Avatar/utils.ts
+++ b/packages/core-components/src/components/Avatar/utils.ts
@@ -22,11 +22,11 @@ export function stringToColor(str: string) {
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
- color += `00${value.toString(16)}`.substr(-2);
+ color += `00${value.toString(16)}`.slice(-2);
}
return color;
}
export function extractInitials(value: string) {
- return value.match(/\b\w/g)?.join('').substring(0, 2);
+ return value.match(/\b\w/g)?.join('').slice(0, 2);
}
diff --git a/plugins/apache-airflow/src/components/ScheduleIntervalLabel/ScheduleIntervalLabel.tsx b/plugins/apache-airflow/src/components/ScheduleIntervalLabel/ScheduleIntervalLabel.tsx
index 14e8148b50..7bd8445766 100644
--- a/plugins/apache-airflow/src/components/ScheduleIntervalLabel/ScheduleIntervalLabel.tsx
+++ b/plugins/apache-airflow/src/components/ScheduleIntervalLabel/ScheduleIntervalLabel.tsx
@@ -26,7 +26,7 @@ const timeDeltaToLabel = (delta: TimeDelta): string => {
let label = '';
const date = new Date(0);
date.setSeconds(delta.seconds);
- const time = date.toISOString().substr(11, 8);
+ const time = date.toISOString().slice(11, 11 + 8);
if (delta.days === 0) {
label = `${time}`;
} else if (delta.days === 1) {
diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts
index 24c13020b6..87ded2f0e9 100644
--- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts
+++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts
@@ -434,7 +434,7 @@ export function mappedRepoBuild(build: Build): RepoBuild {
queueTime: build.queueTime?.toISOString(),
startTime: build.startTime?.toISOString(),
finishTime: build.finishTime?.toISOString(),
- source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`,
+ source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`,
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
};
}
@@ -489,7 +489,7 @@ export function mappedBuildRun(build: Build): BuildRun {
queueTime: build.queueTime?.toISOString(),
startTime: build.startTime?.toISOString(),
finishTime: build.finishTime?.toISOString(),
- source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`,
+ source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`,
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
};
}
diff --git a/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.tsx b/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.tsx
index 0966de3377..3f6ebf93f4 100644
--- a/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.tsx
+++ b/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.tsx
@@ -81,7 +81,7 @@ const renderSource = (build: BitriseBuildResult): React.ReactNode => {
rel="noreferrer"
startIcon={}
>
- {build.commitHash.substr(0, 6)}
+ {build.commitHash.slice(0, 6)}
) : null;
};
diff --git a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.ts b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.ts
index ad63fef810..4852edbb03 100644
--- a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.ts
+++ b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.ts
@@ -132,7 +132,7 @@ export function parseUrl(urlString: string): {
catalogPath: string;
} {
const url = new URL(urlString);
- const path = url.pathname.substr(1).split('/');
+ const path = url.pathname.slice(1).split('/');
const catalogPath = url.searchParams.get('path') || '/catalog-info.yaml';
diff --git a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts
index 6924765483..78aa3eacfd 100644
--- a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts
+++ b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts
@@ -342,7 +342,7 @@ function parseUrl(urlString: string): {
const url = new URL(urlString);
const indexOfProjectSegment =
url.pathname.toLowerCase().indexOf('/projects/') + 1;
- const path = url.pathname.substr(indexOfProjectSegment).split('/');
+ const path = url.pathname.slice(indexOfProjectSegment).split('/');
// /projects/backstage/repos/techdocs-*/catalog-info.yaml
if (path.length > 3 && path[1].length && path[3].length) {
@@ -373,7 +373,7 @@ function parseBitbucketCloudUrl(urlString: string): {
searchEnabled: boolean;
} {
const url = new URL(urlString);
- const pathMap = readPathParameters(url.pathname.substr(1).split('/'));
+ const pathMap = readPathParameters(url.pathname.slice(1).split('/'));
const query = url.searchParams;
if (!pathMap.has('workspaces')) {
diff --git a/plugins/catalog-backend-module-github/src/lib/util.ts b/plugins/catalog-backend-module-github/src/lib/util.ts
index ece463bb40..967a3231b6 100644
--- a/plugins/catalog-backend-module-github/src/lib/util.ts
+++ b/plugins/catalog-backend-module-github/src/lib/util.ts
@@ -17,7 +17,7 @@
import { GithubTopicFilters } from '../providers/GithubEntityProviderConfig';
export function parseGithubOrgUrl(urlString: string): { org: string } {
- const path = new URL(urlString).pathname.substr(1).split('/');
+ const path = new URL(urlString).pathname.slice(1).split('/');
// /backstage
if (path.length === 1 && path[0].length) {
diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts
index 593f5f72f1..3fec87d0b2 100644
--- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts
+++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts
@@ -176,7 +176,7 @@ export function parseUrl(urlString: string): {
host: string;
} {
const url = new URL(urlString);
- const path = url.pathname.substr(1).split('/');
+ const path = url.pathname.slice(1).split('/');
// /backstage/techdocs-*/blob/master/catalog-info.yaml
// can also be
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts
index 1344e2f5d1..01cb701d46 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts
@@ -423,7 +423,7 @@ export function parseUrl(urlString: string): {
host: string;
} {
const url = new URL(urlString);
- const path = url.pathname.substr(1).split('/');
+ const path = url.pathname.slice(1).split('/');
// /backstage/techdocs-*/blob/master/catalog-info.yaml
// can also be
diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts
index b03c665ebc..756093bc56 100644
--- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts
+++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts
@@ -193,7 +193,7 @@ export function parseUrl(urlString: string): {
catalogPath: string;
} {
const url = new URL(urlString);
- const path = url.pathname.substr(1).split('/');
+ const path = url.pathname.slice(1).split('/');
// (/group/subgroup)/blob/branch|*/filepath
const blobIndex = path.findIndex(p => p === 'blob');
diff --git a/plugins/circleci/src/state/useBuilds.ts b/plugins/circleci/src/state/useBuilds.ts
index 760c57d764..1e199cfd34 100644
--- a/plugins/circleci/src/state/useBuilds.ts
+++ b/plugins/circleci/src/state/useBuilds.ts
@@ -64,7 +64,7 @@ const mapSourceDetails = (buildData: BuildSummary) => {
branchName: String(buildData.branch),
commit: {
hash: String(buildData.vcs_revision),
- shortHash: String(buildData.vcs_revision).substr(0, 7),
+ shortHash: String(buildData.vcs_revision).slice(0, 7),
committerName: buildData.committer_name,
url: commitDetails.commit_url,
},
diff --git a/plugins/git-release-manager/src/helpers/getShortCommitHash.ts b/plugins/git-release-manager/src/helpers/getShortCommitHash.ts
index fd53ac7941..3b7a9cc310 100644
--- a/plugins/git-release-manager/src/helpers/getShortCommitHash.ts
+++ b/plugins/git-release-manager/src/helpers/getShortCommitHash.ts
@@ -17,7 +17,7 @@
import { GitReleaseManagerError } from '../errors/GitReleaseManagerError';
export function getShortCommitHash(hash: string) {
- const shortCommitHash = hash.substr(0, 7);
+ const shortCommitHash = hash.slice(0, 7);
if (shortCommitHash.length < 7) {
throw new GitReleaseManagerError(
diff --git a/plugins/playlist-backend/src/service/ListPlaylistsFilter.ts b/plugins/playlist-backend/src/service/ListPlaylistsFilter.ts
index a746286136..680f389f8c 100644
--- a/plugins/playlist-backend/src/service/ListPlaylistsFilter.ts
+++ b/plugins/playlist-backend/src/service/ListPlaylistsFilter.ts
@@ -79,9 +79,9 @@ export function parseListPlaylistsFilterString(
const equalsIndex = statement.indexOf('=');
const key =
- equalsIndex === -1 ? statement : statement.substr(0, equalsIndex).trim();
+ equalsIndex === -1 ? statement : statement.slice(0, equalsIndex).trim();
const value =
- equalsIndex === -1 ? undefined : statement.substr(equalsIndex + 1).trim();
+ equalsIndex === -1 ? undefined : statement.slice(equalsIndex + 1).trim();
if (!key || !value) {
throw new InputError(
diff --git a/plugins/search-backend-module-pg/src/database/util.test.ts b/plugins/search-backend-module-pg/src/database/util.test.ts
index 09eb70c096..b3df5a3ad4 100644
--- a/plugins/search-backend-module-pg/src/database/util.test.ts
+++ b/plugins/search-backend-module-pg/src/database/util.test.ts
@@ -50,7 +50,7 @@ describe('util', () => {
'should get postgres major version, %p',
async databaseId => {
const knex = await databases.init(databaseId);
- const expectedVersion = +databaseId.substr(9);
+ const expectedVersion = +databaseId.slice(9);
await expect(queryPostgresMajorVersion(knex)).resolves.toBe(
expectedVersion,
diff --git a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx
index 015cf96ed1..14e0213bfa 100644
--- a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx
+++ b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx
@@ -22,7 +22,7 @@ import { BackstageTheme } from '@backstage/theme';
import { Link } from '@backstage/core-components';
function stripText(text: string, maxLength: number) {
- return text.length > maxLength ? `${text.substr(0, maxLength)}...` : text;
+ return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
}
const useStyles = makeStyles(theme => ({
root: {
From 6310eacc11efb7b062ea2108f21c9b69bf5fa9f6 Mon Sep 17 00:00:00 2001
From: Magnus Persson
Date: Mon, 23 Jan 2023 00:07:32 +0100
Subject: [PATCH 030/156] Additional package export
Signed-off-by: Magnus Persson
---
.changeset/afraid-foxes-provide.md | 5 ++++
plugins/sonarqube/api-report.md | 27 ++++++++++++++++++++
plugins/sonarqube/package.json | 6 +++--
plugins/sonarqube/src/api/SonarQubeClient.ts | 1 +
plugins/sonarqube/src/index.ts | 1 +
5 files changed, 38 insertions(+), 2 deletions(-)
create mode 100644 .changeset/afraid-foxes-provide.md
diff --git a/.changeset/afraid-foxes-provide.md b/.changeset/afraid-foxes-provide.md
new file mode 100644
index 0000000000..e02702b895
--- /dev/null
+++ b/.changeset/afraid-foxes-provide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-sonarqube': patch
+---
+
+Additional export added in order to bind SonarQubeClient to its apiref
diff --git a/plugins/sonarqube/api-report.md b/plugins/sonarqube/api-report.md
index 31e98a0f4a..f77c66ac59 100644
--- a/plugins/sonarqube/api-report.md
+++ b/plugins/sonarqube/api-report.md
@@ -6,8 +6,12 @@
///
import { BackstagePlugin } from '@backstage/core-plugin-api';
+import { DiscoveryApi } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
+import { FindingSummary } from '@backstage/plugin-sonarqube-react';
+import { IdentityApi } from '@backstage/core-plugin-api';
import { InfoCardVariants } from '@backstage/core-components';
+import { SonarQubeApi } from '@backstage/plugin-sonarqube-react';
// @public (undocumented)
export type DuplicationRating = {
@@ -38,6 +42,29 @@ export const SonarQubeCard: (props: {
duplicationRatings?: DuplicationRating[];
}) => JSX.Element;
+// @alpha (undocumented)
+export class SonarQubeClient implements SonarQubeApi {
+ constructor({
+ discoveryApi,
+ identityApi,
+ }: {
+ discoveryApi: DiscoveryApi;
+ identityApi: IdentityApi;
+ });
+ // (undocumented)
+ discoveryApi: DiscoveryApi;
+ // (undocumented)
+ getFindingSummary({
+ componentKey,
+ projectInstance,
+ }?: {
+ componentKey?: string;
+ projectInstance?: string;
+ }): Promise;
+ // (undocumented)
+ identityApi: IdentityApi;
+}
+
// @public (undocumented)
export type SonarQubeContentPageProps = {
title?: string;
diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json
index af04494906..65142d1c48 100644
--- a/plugins/sonarqube/package.json
+++ b/plugins/sonarqube/package.json
@@ -8,7 +8,8 @@
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
- "types": "dist/index.d.ts"
+ "types": "dist/index.d.ts",
+ "alphaTypes": "dist/index.alpha.d.ts"
},
"backstage": {
"role": "frontend-plugin"
@@ -25,7 +26,7 @@
"sonarcloud"
],
"scripts": {
- "build": "backstage-cli package build",
+ "build": "backstage-cli package build --experimental-type-build",
"start": "backstage-cli package start",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
@@ -64,6 +65,7 @@
},
"files": [
"dist",
+ "alpha",
"config.d.ts"
],
"configSchema": "config.d.ts"
diff --git a/plugins/sonarqube/src/api/SonarQubeClient.ts b/plugins/sonarqube/src/api/SonarQubeClient.ts
index cf893ccef5..f742fe026f 100644
--- a/plugins/sonarqube/src/api/SonarQubeClient.ts
+++ b/plugins/sonarqube/src/api/SonarQubeClient.ts
@@ -23,6 +23,7 @@ import {
import { InstanceUrlWrapper, FindingsWrapper } from './types';
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
+/** @alpha */
export class SonarQubeClient implements SonarQubeApi {
discoveryApi: DiscoveryApi;
identityApi: IdentityApi;
diff --git a/plugins/sonarqube/src/index.ts b/plugins/sonarqube/src/index.ts
index 30f6351d4d..93648caeb5 100644
--- a/plugins/sonarqube/src/index.ts
+++ b/plugins/sonarqube/src/index.ts
@@ -21,5 +21,6 @@
* @packageDocumentation
*/
+export * from './api';
export * from './components';
export * from './plugin';
From f82dad6ebd9e1a23570bf949901b1695d3eddce8 Mon Sep 17 00:00:00 2001
From: Ryan Hanchett
Date: Mon, 23 Jan 2023 15:40:27 -0800
Subject: [PATCH 031/156] chore: set core-components change to patch
Signed-off-by: Ryan Hanchett
---
.changeset/pretty-ladybugs-taste.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/pretty-ladybugs-taste.md b/.changeset/pretty-ladybugs-taste.md
index b4620f4c51..5c87cde123 100644
--- a/.changeset/pretty-ladybugs-taste.md
+++ b/.changeset/pretty-ladybugs-taste.md
@@ -1,5 +1,5 @@
---
-'@backstage/core-components': minor
+'@backstage/core-components': patch
---
Adds new type, TableOptions, extending Material Table Options.
From 89709d523f1419745b3b57707d53e377ac866039 Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Tue, 24 Jan 2023 10:51:45 +0100
Subject: [PATCH 032/156] make refresh private
Signed-off-by: Dominik Pfaffenbauer
---
plugins/catalog-backend-module-gitlab/api-report.md | 2 --
.../src/providers/GitlabOrgDiscoveryEntityProvider.ts | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md
index b69b9904f5..0195975d4b 100644
--- a/plugins/catalog-backend-module-gitlab/api-report.md
+++ b/plugins/catalog-backend-module-gitlab/api-report.md
@@ -71,7 +71,5 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
): GitlabOrgDiscoveryEntityProvider[];
// (undocumented)
getProviderName(): string;
- // (undocumented)
- refresh(logger: Logger): Promise;
}
```
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
index 3b862b6aaa..df480de3cb 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
@@ -156,7 +156,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
};
}
- async refresh(logger: Logger): Promise {
+ private async refresh(logger: Logger): Promise {
if (!this.connection) {
throw new Error(
`Gitlab discovery connection not initialized for ${this.getProviderName()}`,
From 0cf553dcd1e8f7e121cd85cb12ae1ef9f8d0a37f Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Tue, 24 Jan 2023 15:11:49 +0100
Subject: [PATCH 033/156] update docs
Signed-off-by: Marc Bruins
---
.changeset/ninety-hats-serve.md | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/.changeset/ninety-hats-serve.md b/.changeset/ninety-hats-serve.md
index 9d9828f54c..762e53c242 100644
--- a/.changeset/ninety-hats-serve.md
+++ b/.changeset/ninety-hats-serve.md
@@ -2,4 +2,29 @@
'@backstage/plugin-catalog-backend-module-azure': patch
---
-This will add the ability to use Azure DevOps with multi project with a single value which is a new feature as previously this had to be done manually for each project that you wanted to add
+This will add the ability to use Azure DevOps with multi project with a single value which is a new feature as previously this had to be done manually for each project that you wanted to add.
+
+Right now you would have to fill in multiple values in the config to use multiple projects:
+
+```
+yourFirstProviderId: # identifies your dataset / provider independent of config changes
+ organization: myorg
+ project: 'firstProject' # this will match the firstProject project
+ repository: '*' # this will match all repos
+ path: /catalog-info.yaml
+yourSecondProviderId: # identifies your dataset / provider independent of config changes
+ organization: myorg
+ project: 'secondProject' # this will match the secondProject project
+ repository: '*' # this will match all repos
+ path: /catalog-info.yaml
+```
+
+With this change you can actually have all projects available where your PAT determines which you have access to, so that includes multiple projects:
+
+```
+yourFirstProviderId: # identifies your dataset / provider independent of config changes
+ organization: myorg
+ project: '*' # this will match all projects where your PAT has access to
+ repository: '*' # this will match all repos
+ path: /catalog-info.yaml
+```
From e8db0fe4cee7f130a2c4416c05208be1ecac263b Mon Sep 17 00:00:00 2001
From: Marc Bruins
Date: Tue, 24 Jan 2023 15:14:41 +0100
Subject: [PATCH 034/156] update docs
Signed-off-by: Marc Bruins
---
docs/integrations/azure/discovery.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md
index a7ddd9c4a9..7d53e21a89 100644
--- a/docs/integrations/azure/discovery.md
+++ b/docs/integrations/azure/discovery.md
@@ -67,7 +67,7 @@ The parameters available are:
- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host.
- **`organization:`** Your Organization slug (or Collection for on-premise users). Required.
-- **`project:`** Your project slug. Required. Wildcards are supported as show on the examples above. If not set, all projects will be searched.
+- **`project:`** _(optional)_ Your project slug. Wildcards are supported as show on the examples above. If not set, all projects will be searched.
- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched.
- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml.
- **`schedule`** _(optional)_:
From 3283ff18ec733df0735b42578f329432af984792 Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Tue, 24 Jan 2023 15:22:41 +0100
Subject: [PATCH 035/156] change to patch and add missing types for gitlab
config
Signed-off-by: Dominik Pfaffenbauer
---
.changeset/soft-boxes-buy.md | 2 +-
plugins/catalog-backend-module-gitlab/config.d.ts | 12 ++++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/.changeset/soft-boxes-buy.md b/.changeset/soft-boxes-buy.md
index 5e50be4028..581f69cacd 100644
--- a/.changeset/soft-boxes-buy.md
+++ b/.changeset/soft-boxes-buy.md
@@ -1,5 +1,5 @@
---
-'@backstage/plugin-catalog-backend-module-gitlab': minor
+'@backstage/plugin-catalog-backend-module-gitlab': patch
---
Implement Group and User Catalog Provider
diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts
index ac75a04c2a..4f51867efc 100644
--- a/plugins/catalog-backend-module-gitlab/config.d.ts
+++ b/plugins/catalog-backend-module-gitlab/config.d.ts
@@ -48,6 +48,18 @@ export interface Config {
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
+ /**
+ * (Optional) RegExp for the Project Name Pattern
+ */
+ projectPattern?: RegExp;
+ /**
+ * (Optional) RegExp for the User Name Pattern
+ */
+ userPattern?: RegExp;
+ /**
+ * (Optional) RegExp for the Group Name Pattern
+ */
+ groupPattern?: RegExp;
}
>;
};
From 643bf8743c96aed04690681aa94acb40ab6b3124 Mon Sep 17 00:00:00 2001
From: Dominik
Date: Wed, 25 Jan 2023 08:14:58 +0100
Subject: [PATCH 036/156] Update
plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
Co-authored-by: Jamie Klassen
Signed-off-by: Dominik
---
.../src/providers/GitlabOrgDiscoveryEntityProvider.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
index df480de3cb..b4574a8660 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
@@ -170,7 +170,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
const users = paginated(options => client.listUsers(options), {
page: 1,
- per_page: 50,
+ per_page: 100,
active: true,
});
@@ -178,7 +178,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
options => client.listGroups(options),
{
page: 1,
- per_page: 50,
+ per_page: 100,
},
);
From 99dc7dd7e45acbaf5cf251ed0eb658ac215700c6 Mon Sep 17 00:00:00 2001
From: Dominik
Date: Wed, 25 Jan 2023 08:15:04 +0100
Subject: [PATCH 037/156] Update
plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
Co-authored-by: Jamie Klassen
Signed-off-by: Dominik
---
.../src/providers/GitlabOrgDiscoveryEntityProvider.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
index b4574a8660..80286cf42b 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
@@ -50,7 +50,7 @@ type GroupResult = {
};
/**
- * Discovers entity definition files in the groups of a Gitlab instance.
+ * Discovers users and groups from a Gitlab instance.
* @public
*/
export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
From 957365191976d587da5b1c715755c3210fc2dd32 Mon Sep 17 00:00:00 2001
From: Mengnan Gong
Date: Wed, 25 Jan 2023 17:23:24 +0800
Subject: [PATCH 038/156] add a migration script to trigger a reprocessing of
the entities
Signed-off-by: Mengnan Gong
---
.changeset/dull-gorillas-sing.md | 5 +++
.../20230125085746_trigger_reprocessing.js | 33 +++++++++++++++++++
2 files changed, 38 insertions(+)
create mode 100644 .changeset/dull-gorillas-sing.md
create mode 100644 plugins/catalog-backend/migrations/20230125085746_trigger_reprocessing.js
diff --git a/.changeset/dull-gorillas-sing.md b/.changeset/dull-gorillas-sing.md
new file mode 100644
index 0000000000..99246bea1c
--- /dev/null
+++ b/.changeset/dull-gorillas-sing.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+The previous migration that adds the `search.original_value` column may leave some of the entities not updated. Add a migration script to trigger a reprocessing of the entities.
diff --git a/plugins/catalog-backend/migrations/20230125085746_trigger_reprocessing.js b/plugins/catalog-backend/migrations/20230125085746_trigger_reprocessing.js
new file mode 100644
index 0000000000..9bd30723cd
--- /dev/null
+++ b/plugins/catalog-backend/migrations/20230125085746_trigger_reprocessing.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// @ts-check
+
+/**
+ * @param { import("knex").Knex } knex
+ */
+exports.up = async function up(knex) {
+ await knex('final_entities').update({ hash: '' });
+ await knex('refresh_state').update({
+ result_hash: '',
+ next_update_at: knex.fn.now(),
+ });
+};
+
+/**
+ * @param { import("knex").Knex } _knex
+ */
+exports.down = async function down(_knex) {};
From ea0f19229aa7b71245958ff59687b1861541117f Mon Sep 17 00:00:00 2001
From: Costas Drogos
Date: Wed, 25 Jan 2023 12:14:47 +0100
Subject: [PATCH 039/156] GithubDiscoveryProcessor: allow filtering on forks
Forks of repositories containing a catalog yml, might override discovery for
the upstream repository, if both are discovered.
This might not be desired in environments working extensively with forks, as it
can create a lot of confusion and potentially, breakage if fork and upstream end
up with different versions of the catalog yaml.
This changeset provides a way to enable or disable evaluating forked repositories
via Github's API `isFork` attribute.
In detail, this changeset introduces a new config option, namely `allowForks`
(boolean, defaulting to true) and the underlying logic, that will exclude any
forked repository from the list of repositories to be discovered, when set to false.
Signed-off-by: Costas Drogos
---
.../catalog-backend-module-github/config.d.ts | 8 +++++
.../src/lib/github.test.ts | 4 +++
.../src/lib/github.ts | 2 ++
.../src/lib/util.test.ts | 24 ++++++++++++-
.../src/lib/util.ts | 11 ++++++
.../GithubDiscoveryProcessor.test.ts | 13 +++++++
.../providers/GithubEntityProvider.test.ts | 8 +++++
.../src/providers/GithubEntityProvider.ts | 7 +++-
.../GithubEntityProviderConfig.test.ts | 34 +++++++++++++++++--
.../providers/GithubEntityProviderConfig.ts | 3 ++
10 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts
index 691caedc46..37852223c1 100644
--- a/plugins/catalog-backend-module-github/config.d.ts
+++ b/plugins/catalog-backend-module-github/config.d.ts
@@ -86,6 +86,10 @@ export interface Config {
/**
* (Optional) GitHub topic-based filters.
*/
+ allowForks?: boolean;
+ /**
+ * (Optional) Allow Forks to be evaluated.
+ */
topic?: {
/**
* (Optional) An array of strings used to filter in results based on their associated GitHub topics.
@@ -143,6 +147,10 @@ export interface Config {
/**
* (Optional) GitHub topic-based filters.
*/
+ allowForks?: boolean;
+ /**
+ * (Optional) Allow Forks to be evaluated.
+ */
topic?: {
/**
* (Optional) An array of strings used to filter in results based on their associated GitHub topics.
diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts
index 387dc8a2ee..624ea5c3c9 100644
--- a/plugins/catalog-backend-module-github/src/lib/github.test.ts
+++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts
@@ -488,6 +488,7 @@ describe('github', () => {
name: 'backstage',
url: 'https://github.com/backstage/backstage',
isArchived: false,
+ isFork: false,
repositoryTopics: {
nodes: [{ topic: { name: 'blah' } }],
},
@@ -500,6 +501,7 @@ describe('github', () => {
name: 'demo',
url: 'https://github.com/backstage/demo',
isArchived: true,
+ isFork: true,
repositoryTopics: { nodes: [] },
defaultBranchRef: {
name: 'main',
@@ -524,6 +526,7 @@ describe('github', () => {
name: 'backstage',
url: 'https://github.com/backstage/backstage',
isArchived: false,
+ isFork: false,
repositoryTopics: {
nodes: [{ topic: { name: 'blah' } }],
},
@@ -536,6 +539,7 @@ describe('github', () => {
name: 'demo',
url: 'https://github.com/backstage/demo',
isArchived: true,
+ isFork: true,
repositoryTopics: { nodes: [] },
defaultBranchRef: {
name: 'main',
diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts
index f719e3d506..ae6e14fc23 100644
--- a/plugins/catalog-backend-module-github/src/lib/github.ts
+++ b/plugins/catalog-backend-module-github/src/lib/github.ts
@@ -89,6 +89,7 @@ export type RepositoryResponse = {
name: string;
url: string;
isArchived: boolean;
+ isFork: boolean;
repositoryTopics: RepositoryTopics;
defaultBranchRef: {
name: string;
@@ -435,6 +436,7 @@ export async function getOrganizationRepositories(
}
url
isArchived
+ isFork
repositoryTopics(first: 100) {
nodes {
... on RepositoryTopic {
diff --git a/plugins/catalog-backend-module-github/src/lib/util.test.ts b/plugins/catalog-backend-module-github/src/lib/util.test.ts
index 7aca6839ea..0de1d24bbf 100644
--- a/plugins/catalog-backend-module-github/src/lib/util.test.ts
+++ b/plugins/catalog-backend-module-github/src/lib/util.test.ts
@@ -15,7 +15,11 @@
*/
import { GithubTopicFilters } from '../providers/GithubEntityProviderConfig';
-import { parseGithubOrgUrl, satisfiesTopicFilter } from './util';
+import {
+ parseGithubOrgUrl,
+ satisfiesTopicFilter,
+ satisfiesForkFilter,
+} from './util';
describe('parseGithubOrgUrl', () => {
it('only supports clean org urls, and decodes them', () => {
@@ -87,3 +91,21 @@ describe('satisfiesTopicFilter', () => {
).toEqual(false);
});
});
+
+describe('satisfiesForkFilter', () => {
+ it('handles cases where forks are not allowed and a fork is evaluated', () => {
+ expect(satisfiesForkFilter(false, true)).toEqual(false);
+ });
+
+ it('handles cases where forks are not allowed and a fork is not evaluated', () => {
+ expect(satisfiesForkFilter(false, false)).toEqual(true);
+ });
+
+ it('handles cases where forks are allowed and a fork is evaluated', () => {
+ expect(satisfiesForkFilter(true, true)).toEqual(true);
+ });
+
+ it('handles cases where forks are allowed and a fork is not evaluated', () => {
+ expect(satisfiesForkFilter(true, false)).toEqual(true);
+ });
+});
diff --git a/plugins/catalog-backend-module-github/src/lib/util.ts b/plugins/catalog-backend-module-github/src/lib/util.ts
index ece463bb40..5c0297c0cc 100644
--- a/plugins/catalog-backend-module-github/src/lib/util.ts
+++ b/plugins/catalog-backend-module-github/src/lib/util.ts
@@ -77,3 +77,14 @@ export function satisfiesTopicFilter(
// not configured at all.
return true;
}
+
+export function satisfiesForkFilter(
+ allowForks: boolean | true,
+ isFork: boolean | false,
+): Boolean {
+ // we don't want to include forks if forks are not allowed
+ if (!allowForks && isFork) return false;
+
+ // if forks are allowed, allow all repos through
+ return true;
+}
diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts
index b69728cc62..f30007c677 100644
--- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts
+++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts
@@ -150,6 +150,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/backstage',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'master',
},
@@ -160,6 +161,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/demo',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -202,6 +204,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/tech-docs',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -236,6 +239,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/tech-docs',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: null,
catalogInfoFile: null,
},
@@ -260,6 +264,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/backstage',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'master',
},
@@ -295,6 +300,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/backstage',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -305,6 +311,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/techdocs-cli',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -315,6 +322,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/techdocs-container',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -325,6 +333,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/techdocs-durp',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: null,
catalogInfoFile: null,
},
@@ -365,6 +374,7 @@ describe('GithubDiscoveryProcessor', () => {
name: 'abstest',
url: 'https://github.com/backstage/abctest',
isArchived: false,
+ isFork: false,
repositoryTopics: { nodes: [] },
defaultBranchRef: {
name: 'main',
@@ -375,6 +385,7 @@ describe('GithubDiscoveryProcessor', () => {
name: 'test',
url: 'https://github.com/backstage/test',
isArchived: false,
+ isFork: false,
repositoryTopics: { nodes: [] },
defaultBranchRef: {
name: 'main',
@@ -386,6 +397,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/test',
repositoryTopics: { nodes: [] },
isArchived: true,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -396,6 +408,7 @@ describe('GithubDiscoveryProcessor', () => {
url: 'https://github.com/backstage/testxyz',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts
index 9916825cdc..2be5df09fe 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts
@@ -172,6 +172,7 @@ describe('GithubEntityProvider', () => {
url: 'https://github.com/test-org/test-repo',
repositoryTopics: { nodes: [] },
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -274,6 +275,7 @@ describe('GithubEntityProvider', () => {
],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -353,6 +355,7 @@ describe('GithubEntityProvider', () => {
],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -445,6 +448,7 @@ describe('GithubEntityProvider', () => {
nodes: [],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -457,6 +461,7 @@ describe('GithubEntityProvider', () => {
nodes: [],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -557,6 +562,7 @@ describe('GithubEntityProvider', () => {
],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -580,6 +586,7 @@ describe('GithubEntityProvider', () => {
],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
@@ -600,6 +607,7 @@ describe('GithubEntityProvider', () => {
],
},
isArchived: false,
+ isFork: false,
defaultBranchRef: {
name: 'main',
},
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts
index 1344e2f5d1..f84f401191 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts
@@ -40,7 +40,7 @@ import {
GithubEntityProviderConfig,
} from './GithubEntityProviderConfig';
import { getOrganizationRepositories } from '../lib/github';
-import { satisfiesTopicFilter } from '../lib/util';
+import { satisfiesTopicFilter, satisfiesForkFilter } from '../lib/util';
import { EventParams, EventSubscriber } from '@backstage/plugin-events-node';
import { PushEvent, Commit } from '@octokit/webhooks-types';
@@ -52,6 +52,7 @@ type Repository = {
name: string;
url: string;
isArchived: boolean;
+ isFork: boolean;
repositoryTopics: string[];
defaultBranchRef?: string;
isCatalogInfoFilePresent: boolean;
@@ -216,6 +217,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber {
defaultBranchRef: r.defaultBranchRef?.name,
repositoryTopics: r.repositoryTopics.nodes.map(t => t.topic.name),
isArchived: r.isArchived,
+ isFork: r.isFork,
isCatalogInfoFilePresent:
r.catalogInfoFile?.__typename === 'Blob' &&
r.catalogInfoFile.text !== '',
@@ -234,6 +236,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber {
private matchesFilters(repositories: Repository[]) {
const repositoryFilter = this.config.filters?.repository;
const topicFilters = this.config.filters?.topic;
+ const allowForks = this.config.filters?.allowForks ?? true;
const matchingRepositories = repositories.filter(r => {
const repoTopics: string[] = r.repositoryTopics;
@@ -241,6 +244,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber {
!r.isArchived &&
(!repositoryFilter || repositoryFilter.test(r.name)) &&
satisfiesTopicFilter(repoTopics, topicFilters) &&
+ satisfiesForkFilter(allowForks, r.isFork) &&
r.defaultBranchRef
);
});
@@ -302,6 +306,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber {
defaultBranchRef: event.repository.default_branch,
repositoryTopics: event.repository.topics,
isArchived: event.repository.archived,
+ isFork: event.repository.fork,
// we can consider this file present because
// only the catalog file will be recovered from the commits
isCatalogInfoFilePresent: true,
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts
index 0d9b5ce65b..a597e9dbe0 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts
@@ -78,6 +78,12 @@ describe('readProviderConfigs', () => {
},
},
},
+ providerWithForkFilter: {
+ organization: 'test-org6',
+ filters: {
+ allowForks: false,
+ },
+ },
providerWithHost: {
organization: 'test-org1',
host: 'ghe.internal.com',
@@ -97,7 +103,7 @@ describe('readProviderConfigs', () => {
});
const providerConfigs = readProviderConfigs(config);
- expect(providerConfigs).toHaveLength(7);
+ expect(providerConfigs).toHaveLength(8);
expect(providerConfigs[0]).toEqual({
id: 'providerOrganizationOnly',
organization: 'test-org1',
@@ -106,6 +112,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
+ allowForks: true,
topic: {
include: undefined,
exclude: undefined,
@@ -122,6 +129,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
+ allowForks: true,
topic: {
include: undefined,
exclude: undefined,
@@ -138,6 +146,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: /^repository.*filter$/, // repo
branch: undefined, // branch
+ allowForks: true,
topic: {
include: undefined,
exclude: undefined,
@@ -154,6 +163,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: 'branch-name',
+ allowForks: true,
topic: {
include: undefined,
exclude: undefined,
@@ -170,6 +180,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
+ allowForks: true,
topic: {
include: ['backstage-include'],
exclude: ['backstage-exclude'],
@@ -179,6 +190,23 @@ describe('readProviderConfigs', () => {
validateLocationsExist: false,
});
expect(providerConfigs[5]).toEqual({
+ id: 'providerWithForkFilter',
+ organization: 'test-org6',
+ catalogPath: '/catalog-info.yaml',
+ host: 'github.com',
+ filters: {
+ repository: undefined,
+ branch: undefined,
+ allowForks: false,
+ topic: {
+ include: undefined,
+ exclude: undefined,
+ },
+ },
+ schedule: undefined,
+ validateLocationsExist: false,
+ });
+ expect(providerConfigs[6]).toEqual({
id: 'providerWithHost',
organization: 'test-org1',
catalogPath: '/catalog-info.yaml',
@@ -186,6 +214,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
+ allowForks: true,
topic: {
include: undefined,
exclude: undefined,
@@ -194,7 +223,7 @@ describe('readProviderConfigs', () => {
validateLocationsExist: false,
schedule: undefined,
});
- expect(providerConfigs[6]).toEqual({
+ expect(providerConfigs[7]).toEqual({
id: 'providerWithSchedule',
organization: 'test-org1',
catalogPath: '/catalog-info.yaml',
@@ -202,6 +231,7 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
+ allowForks: true,
topic: {
include: undefined,
exclude: undefined,
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts
index e888743258..a79a557ebf 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts
@@ -32,6 +32,7 @@ export type GithubEntityProviderConfig = {
repository?: RegExp;
branch?: string;
topic?: GithubTopicFilters;
+ allowForks?: boolean;
};
validateLocationsExist: boolean;
schedule?: TaskScheduleDefinition;
@@ -72,6 +73,7 @@ function readProviderConfig(
const host = config.getOptionalString('host') ?? 'github.com';
const repositoryPattern = config.getOptionalString('filters.repository');
const branchPattern = config.getOptionalString('filters.branch');
+ const allowForks = config.getOptionalBoolean('filters.allowForks') ?? true;
const topicFilterInclude = config?.getOptionalStringArray(
'filters.topic.include',
);
@@ -103,6 +105,7 @@ function readProviderConfig(
? compileRegExp(repositoryPattern)
: undefined,
branch: branchPattern || undefined,
+ allowForks: allowForks,
topic: {
include: topicFilterInclude,
exclude: topicFilterExclude,
From 66158754b4aa18579f13e5b8d088d850a38d7b5f Mon Sep 17 00:00:00 2001
From: Costas Drogos
Date: Wed, 25 Jan 2023 12:18:57 +0100
Subject: [PATCH 040/156] Add changeset
Signed-off-by: Costas Drogos
---
.changeset/hot-lions-cover.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/hot-lions-cover.md
diff --git a/.changeset/hot-lions-cover.md b/.changeset/hot-lions-cover.md
new file mode 100644
index 0000000000..62064188a4
--- /dev/null
+++ b/.changeset/hot-lions-cover.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-github': patch
+---
+
+Add support for filtering out forks
From d8249ea345a6e47a5ef0925d0268014399206583 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 25 Jan 2023 12:07:36 +0000
Subject: [PATCH 041/156] fix(deps): update dependency @types/express to
v4.17.16
Signed-off-by: Renovate Bot
---
yarn.lock | 84 +++++++++++++++++++++++++++----------------------------
1 file changed, 42 insertions(+), 42 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index d0d2509f30..2a4af9a6f2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3398,7 +3398,7 @@ __metadata:
"@manypkg/get-packages": ^1.1.3
"@types/compression": ^1.7.0
"@types/cors": ^2.8.6
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/fs-extra": ^9.0.3
"@types/http-errors": ^2.0.0
"@types/minimist": ^1.2.0
@@ -3452,7 +3452,7 @@ __metadata:
"@types/concat-stream": ^2.0.0
"@types/cors": ^2.8.6
"@types/dockerode": ^3.3.0
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/fs-extra": ^9.0.3
"@types/http-errors": ^2.0.0
"@types/luxon": ^3.0.0
@@ -3537,7 +3537,7 @@ __metadata:
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/types": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
express: ^4.17.1
knex: ^2.0.0
languageName: unknown
@@ -3691,7 +3691,7 @@ __metadata:
"@swc/helpers": ^0.4.7
"@swc/jest": ^0.2.22
"@types/diff": ^5.0.0
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/fs-extra": ^9.0.1
"@types/http-proxy": ^1.17.4
"@types/inquirer": ^8.1.3
@@ -4201,7 +4201,7 @@ __metadata:
"@backstage/backend-common": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/http-proxy-middleware": ^0.19.3
"@types/supertest": ^2.0.8
express: ^4.17.1
@@ -4417,7 +4417,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/config-loader": "workspace:^"
"@backstage/types": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -4453,7 +4453,7 @@ __metadata:
"@google-cloud/firestore": ^6.0.0
"@types/body-parser": ^1.19.0
"@types/cookie-parser": ^1.4.2
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/express-session": ^1.17.2
"@types/jwt-decode": ^3.1.0
"@types/passport": ^1.0.3
@@ -4509,7 +4509,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
express: ^4.17.1
jose: ^4.6.0
lodash: ^4.17.21
@@ -4528,7 +4528,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/plugin-azure-devops-common": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
azure-devops-node-api: ^11.0.1
express: ^4.17.1
@@ -4594,7 +4594,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/plugin-azure-sites-common": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -4653,7 +4653,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
badge-maker: ^3.3.0
cors: ^2.8.5
@@ -4705,7 +4705,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
express: ^4.17.1
express-promise-router: ^4.1.0
knex: ^2.0.0
@@ -5017,7 +5017,7 @@ __metadata:
"@backstage/plugin-catalog-node": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/luxon": ^3.0.0
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -5120,7 +5120,7 @@ __metadata:
"@backstage/types": "workspace:^"
"@opentelemetry/api": ^1.3.0
"@types/core-js": ^2.5.4
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/git-url-parse": ^9.0.0
"@types/lodash": ^4.14.151
"@types/supertest": ^2.0.8
@@ -5562,7 +5562,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/express-xml-bodyparser": ^0.3.2
"@types/supertest": ^2.0.8
express: ^4.17.1
@@ -5872,7 +5872,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/plugin-events-backend-test-utils": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
express: ^4.17.1
express-promise-router: ^4.1.0
supertest: ^6.1.3
@@ -5898,7 +5898,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/plugin-explore-common": "workspace:^"
"@backstage/plugin-search-common": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.18.1
express-promise-router: ^4.1.0
@@ -6364,7 +6364,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/plugin-catalog-graphql": "workspace:^"
"@graphql-tools/schema": ^9.0.0
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
apollo-server: ^3.0.0
apollo-server-express: ^3.0.0
@@ -6484,7 +6484,7 @@ __metadata:
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-jenkins-common": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/jenkins": ^0.23.1
"@types/supertest": ^2.0.8
express: ^4.17.1
@@ -6549,7 +6549,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/jest-when": ^3.5.0
"@types/lodash": ^4.14.151
express: ^4.17.1
@@ -6612,7 +6612,7 @@ __metadata:
"@jest-mock/express": ^2.0.1
"@kubernetes/client-node": 0.18.1
"@types/aws4": ^1.5.1
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/http-proxy-middleware": ^0.19.3
"@types/luxon": ^3.0.0
aws-sdk: ^2.840.0
@@ -6869,7 +6869,7 @@ __metadata:
"@backstage/backend-common": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -6924,7 +6924,7 @@ __metadata:
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/lodash": ^4.14.151
"@types/supertest": ^2.0.8
dataloader: ^2.0.0
@@ -6966,7 +6966,7 @@ __metadata:
"@backstage/errors": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -7013,7 +7013,7 @@ __metadata:
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/plugin-playlist-common": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -7084,7 +7084,7 @@ __metadata:
"@backstage/backend-common": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/http-proxy-middleware": ^0.19.3
"@types/supertest": ^2.0.8
"@types/uuid": ^8.0.0
@@ -7111,7 +7111,7 @@ __metadata:
"@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
camelcase-keys: ^7.0.1
compression: ^1.7.4
@@ -7270,7 +7270,7 @@ __metadata:
"@gitbeaker/node": ^35.1.0
"@octokit/webhooks": ^10.0.0
"@types/command-exists": ^1.2.0
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/fs-extra": ^9.0.1
"@types/git-url-parse": ^9.0.0
"@types/mock-fs": ^4.13.0
@@ -7529,7 +7529,7 @@ __metadata:
"@backstage/plugin-search-backend-node": "workspace:^"
"@backstage/plugin-search-common": "workspace:^"
"@backstage/types": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
dataloader: ^2.0.0
express: ^4.17.1
@@ -7696,7 +7696,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/test-utils": "workspace:^"
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.12
express: ^4.18.1
express-promise-router: ^4.1.0
@@ -7890,7 +7890,7 @@ __metadata:
"@backstage/plugin-tech-insights-common": "workspace:^"
"@backstage/plugin-tech-insights-node": "workspace:^"
"@backstage/types": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/luxon": ^3.0.0
"@types/semver": ^7.3.8
"@types/supertest": ^2.0.8
@@ -8054,7 +8054,7 @@ __metadata:
"@backstage/plugin-search-common": "workspace:^"
"@backstage/plugin-techdocs-node": "workspace:^"
"@types/dockerode": ^3.3.0
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
dockerode: ^3.3.1
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -8122,7 +8122,7 @@ __metadata:
"@backstage/plugin-search-common": "workspace:^"
"@google-cloud/storage": ^6.0.0
"@trendyol-js/openstack-swift-sdk": ^0.0.5
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/fs-extra": ^9.0.5
"@types/js-yaml": ^4.0.0
"@types/mime-types": ^2.1.0
@@ -8234,7 +8234,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -8287,7 +8287,7 @@ __metadata:
"@backstage/errors": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/types": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
express: ^4.17.1
express-promise-router: ^4.1.0
@@ -8341,7 +8341,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@types/compression": ^1.7.2
- "@types/express": "*"
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
compression: ^1.7.4
cors: ^2.8.5
@@ -10275,7 +10275,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/supertest": ^2.0.8
"@types/uuid": ^8.0.0
express: ^4.17.1
@@ -14390,15 +14390,15 @@ __metadata:
languageName: node
linkType: hard
-"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.15, @types/express@npm:^4.17.6":
- version: 4.17.15
- resolution: "@types/express@npm:4.17.15"
+"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.15":
+ version: 4.17.16
+ resolution: "@types/express@npm:4.17.16"
dependencies:
"@types/body-parser": "*"
"@types/express-serve-static-core": ^4.17.31
"@types/qs": "*"
"@types/serve-static": "*"
- checksum: b4acd8a836d4f6409cdf79b12d6e660485249b62500cccd61e7997d2f520093edf77d7f8498ca79d64a112c6434b6de5ca48039b8fde2c881679eced7e96979b
+ checksum: 43f3ed2cea6e5e83c7c1098c5152f644e975fd764443717ff9c812a1518416a9e7e9f824ffe852c118888cbfb994ed023cad08331f49b19ced469bb185cdd5cd
languageName: node
linkType: hard
@@ -22463,7 +22463,7 @@ __metadata:
"@gitbeaker/node": ^35.1.0
"@octokit/rest": ^19.0.3
"@types/dockerode": ^3.3.0
- "@types/express": ^4.17.6
+ "@types/express": ^4.17.15
"@types/express-serve-static-core": ^4.17.5
"@types/luxon": ^3.0.0
azure-devops-node-api: ^11.0.1
From ae88f61e00dc21efeca3cbf5e09a4c411ad747c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 13:47:27 +0100
Subject: [PATCH 042/156] break into dedicated plugin and module registration
points
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
.changeset/sharp-lobsters-build.md | 10 +++++
packages/backend-plugin-api/api-report.md | 43 ++++++++++++++++---
.../src/wiring/factories.ts | 13 +++---
.../backend-plugin-api/src/wiring/index.ts | 2 +
.../backend-plugin-api/src/wiring/types.ts | 40 ++++++++++++++++-
5 files changed, 96 insertions(+), 12 deletions(-)
create mode 100644 .changeset/sharp-lobsters-build.md
diff --git a/.changeset/sharp-lobsters-build.md b/.changeset/sharp-lobsters-build.md
new file mode 100644
index 0000000000..f9d317a1f6
--- /dev/null
+++ b/.changeset/sharp-lobsters-build.md
@@ -0,0 +1,10 @@
+---
+'@backstage/backend-plugin-api': minor
+---
+
+The `register` methods passed to `createBackendPlugin` and `createBackendModule`
+now have dedicated `BackendPluginRegistrationPoints` and
+`BackendModuleRegistrationPoints` arguments, respectively. This lets us make it
+clear on a type level that it's not possible to pass in extension points as
+dependencies to plugins (should only ever be done for modules). This has no
+practical effect on code that was already well behaved.
diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md
index 86917dae11..4543e5b580 100644
--- a/packages/backend-plugin-api/api-report.md
+++ b/packages/backend-plugin-api/api-report.md
@@ -29,9 +29,22 @@ export interface BackendModuleConfig {
// (undocumented)
pluginId: string;
// (undocumented)
- register(
- reg: Omit,
- ): void;
+ register(reg: BackendModuleRegistrationPoints): void;
+}
+
+// @public
+export interface BackendModuleRegistrationPoints {
+ // (undocumented)
+ registerInit<
+ Deps extends {
+ [name in string]: unknown;
+ },
+ >(options: {
+ deps: {
+ [name in keyof Deps]: ServiceRef | ExtensionPoint;
+ };
+ init(deps: Deps): Promise;
+ }): void;
}
// @public (undocumented)
@@ -39,10 +52,30 @@ export interface BackendPluginConfig {
// (undocumented)
id: string;
// (undocumented)
- register(reg: BackendRegistrationPoints): void;
+ register(reg: BackendPluginRegistrationPoints): void;
}
-// @public (undocumented)
+// @public
+export interface BackendPluginRegistrationPoints {
+ // (undocumented)
+ registerExtensionPoint(
+ ref: ExtensionPoint,
+ impl: TExtensionPoint,
+ ): void;
+ // (undocumented)
+ registerInit<
+ Deps extends {
+ [name in string]: unknown;
+ },
+ >(options: {
+ deps: {
+ [name in keyof Deps]: ServiceRef;
+ };
+ init(deps: Deps): Promise;
+ }): void;
+}
+
+// @public
export interface BackendRegistrationPoints {
// (undocumented)
registerExtensionPoint(
diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts
index ad2e8edd13..b7482a9f0b 100644
--- a/packages/backend-plugin-api/src/wiring/factories.ts
+++ b/packages/backend-plugin-api/src/wiring/factories.ts
@@ -15,6 +15,8 @@
*/
import {
+ BackendModuleRegistrationPoints,
+ BackendPluginRegistrationPoints,
BackendRegistrationPoints,
BackendFeature,
ExtensionPoint,
@@ -44,7 +46,7 @@ export function createExtensionPoint(
/** @public */
export interface BackendPluginConfig {
id: string;
- register(reg: BackendRegistrationPoints): void;
+ register(reg: BackendPluginRegistrationPoints): void;
}
/** @public */
@@ -62,9 +64,7 @@ export function createBackendPlugin(
export interface BackendModuleConfig {
pluginId: string;
moduleId: string;
- register(
- reg: Omit,
- ): void;
+ register(reg: BackendModuleRegistrationPoints): void;
}
/**
@@ -96,8 +96,9 @@ export function createBackendModule(
return () => ({
id: `${config.pluginId}.${config.moduleId}`,
register(register: BackendRegistrationPoints) {
- // TODO: Hide registerExtensionPoint
- return config.register(register);
+ return config.register({
+ registerInit: register.registerInit.bind(register),
+ });
},
});
}
diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts
index 0d5999ad12..e0c25eeb4e 100644
--- a/packages/backend-plugin-api/src/wiring/index.ts
+++ b/packages/backend-plugin-api/src/wiring/index.ts
@@ -30,6 +30,8 @@ export {
createExtensionPoint,
} from './factories';
export type {
+ BackendModuleRegistrationPoints,
+ BackendPluginRegistrationPoints,
BackendRegistrationPoints,
BackendFeature,
ExtensionPoint,
diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts
index cdb56ea2e4..6e081f57e5 100644
--- a/packages/backend-plugin-api/src/wiring/types.ts
+++ b/packages/backend-plugin-api/src/wiring/types.ts
@@ -35,7 +35,13 @@ export type ExtensionPoint = {
$$ref: 'extension-point';
};
-/** @public */
+/**
+ * The callbacks passed to the `register` method of a backend feature; this is
+ * essentially a superset of {@link BackendPluginRegistrationPoints} and
+ * {@link BackendModuleRegistrationPoints}.
+ *
+ * @public
+ */
export interface BackendRegistrationPoints {
registerExtensionPoint(
ref: ExtensionPoint,
@@ -49,6 +55,38 @@ export interface BackendRegistrationPoints {
}): void;
}
+/**
+ * The callbacks passed to the `register` method of a backend plugin.
+ *
+ * @public
+ */
+export interface BackendPluginRegistrationPoints {
+ registerExtensionPoint(
+ ref: ExtensionPoint,
+ impl: TExtensionPoint,
+ ): void;
+ registerInit(options: {
+ deps: {
+ [name in keyof Deps]: ServiceRef;
+ };
+ init(deps: Deps): Promise;
+ }): void;
+}
+
+/**
+ * The callbacks passed to the `register` method of a backend module.
+ *
+ * @public
+ */
+export interface BackendModuleRegistrationPoints {
+ registerInit(options: {
+ deps: {
+ [name in keyof Deps]: ServiceRef | ExtensionPoint;
+ };
+ init(deps: Deps): Promise;
+ }): void;
+}
+
/** @public */
export interface BackendFeature {
// TODO(Rugvip): Try to get rid of the ID at this level, allowing for a feature to register multiple features as a bundle
From 46d5d73bf815deb600fe04591f26ab7c21846e7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 14:34:28 +0100
Subject: [PATCH 043/156] update some backend system doc comments
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
packages/backend-plugin-api/api-report.md | 18 ++---
.../src/wiring/createSharedEnvironment.ts | 34 +++++++---
.../src/wiring/factories.ts | 68 +++++++++++++++----
3 files changed, 84 insertions(+), 36 deletions(-)
diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md
index 86917dae11..da30e3a1d5 100644
--- a/packages/backend-plugin-api/api-report.md
+++ b/packages/backend-plugin-api/api-report.md
@@ -22,11 +22,9 @@ export interface BackendFeature {
register(reg: BackendRegistrationPoints): void;
}
-// @public (undocumented)
+// @public
export interface BackendModuleConfig {
- // (undocumented)
moduleId: string;
- // (undocumented)
pluginId: string;
// (undocumented)
register(
@@ -34,9 +32,8 @@ export interface BackendModuleConfig {
): void;
}
-// @public (undocumented)
+// @public
export interface BackendPluginConfig {
- // (undocumented)
id: string;
// (undocumented)
register(reg: BackendRegistrationPoints): void;
@@ -116,12 +113,12 @@ export function createBackendModule(
config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
): (...params: TOptions) => BackendFeature;
-// @public (undocumented)
+// @public
export function createBackendPlugin(
config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
): (...params: TOptions) => BackendFeature;
-// @public (undocumented)
+// @public
export function createExtensionPoint(
config: ExtensionPointConfig,
): ExtensionPoint;
@@ -248,9 +245,8 @@ export type ExtensionPoint = {
$$ref: 'extension-point';
};
-// @public (undocumented)
+// @public
export interface ExtensionPointConfig {
- // (undocumented)
id: string;
}
@@ -476,13 +472,13 @@ export interface ServiceRefConfig {
scope?: TScope;
}
-// @public (undocumented)
+// @public
export interface SharedBackendEnvironment {
// (undocumented)
$$type: 'SharedBackendEnvironment';
}
-// @public (undocumented)
+// @public
export interface SharedBackendEnvironmentConfig {
// (undocumented)
services?: ServiceFactoryOrFunction[];
diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts
index 3258188772..2eb2cdec6a 100644
--- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts
+++ b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts
@@ -16,35 +16,47 @@
import { ServiceFactory, ServiceFactoryOrFunction } from '../services';
-/** @public */
+/**
+ * The configuration options passed to {@link createSharedEnvironment}.
+ *
+ * @public
+ */
export interface SharedBackendEnvironmentConfig {
services?: ServiceFactoryOrFunction[];
}
-// This type is opaque in order to allow for future API evolution without
-// cluttering the external API. For example we might want to add support
-// for more powerful callback based backend modifications.
-//
-// By making this opaque we also ensure that the type doesn't become an input
-// type that we need to care about, as it would otherwise be possible to pass
-// a custom environment definition to `createBackend`, which we don't want.
/**
+ * An opaque type that represents the contents of a shared backend environment.
+ *
* @public
*/
export interface SharedBackendEnvironment {
$$type: 'SharedBackendEnvironment';
+
+ // NOTE: This type is opaque in order to allow for future API evolution without
+ // cluttering the external API. For example we might want to add support
+ // for more powerful callback based backend modifications.
+ //
+ // By making this opaque we also ensure that the type doesn't become an input
+ // type that we need to care about, as it would otherwise be possible to pass
+ // a custom environment definition to `createBackend`, which we don't want.
}
/**
- * This type is NOT supposed to be used by anyone except internally by the backend-app-api package.
- * @internal */
+ * This type is NOT supposed to be used by anyone except internally by the
+ * backend-app-api package.
+ *
+ * @internal
+ */
export interface InternalSharedBackendEnvironment {
version: 'v1';
services?: ServiceFactory[];
}
/**
- * Creates a shared backend environment which can be used to create multiple backends
+ * Creates a shared backend environment which can be used to create multiple
+ * backends.
+ *
* @public
*/
export function createSharedEnvironment<
diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts
index ad2e8edd13..dd0240b71c 100644
--- a/packages/backend-plugin-api/src/wiring/factories.ts
+++ b/packages/backend-plugin-api/src/wiring/factories.ts
@@ -20,12 +20,28 @@ import {
ExtensionPoint,
} from './types';
-/** @public */
+/**
+ * The configuration options passed to {@link createExtensionPoint}.
+ *
+ * @public
+ * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points}
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
export interface ExtensionPointConfig {
+ /**
+ * The ID of this extension point.
+ *
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
id: string;
}
-/** @public */
+/**
+ * Creates a new backend extension point.
+ *
+ * @public
+ * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points}
+ */
export function createExtensionPoint(
config: ExtensionPointConfig,
): ExtensionPoint {
@@ -41,13 +57,30 @@ export function createExtensionPoint(
};
}
-/** @public */
+/**
+ * The configuration options passed to {@link createBackendPlugin}.
+ *
+ * @public
+ * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins}
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
export interface BackendPluginConfig {
+ /**
+ * The ID of this plugin.
+ *
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
id: string;
register(reg: BackendRegistrationPoints): void;
}
-/** @public */
+/**
+ * Creates a new backend plugin.
+ *
+ * @public
+ * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins}
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
export function createBackendPlugin(
config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
): (...params: TOptions) => BackendFeature {
@@ -58,9 +91,23 @@ export function createBackendPlugin(
return () => config;
}
-/** @public */
+/**
+ * The configuration options passed to {@link createBackendModule}.
+ *
+ * @public
+ * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules}
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
export interface BackendModuleConfig {
+ /**
+ * The ID of this plugin.
+ *
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
+ */
pluginId: string;
+ /**
+ * Should exactly match the `id` of the plugin that the module extends.
+ */
moduleId: string;
register(
reg: Omit,
@@ -71,15 +118,8 @@ export interface BackendModuleConfig {
* Creates a new backend module for a given plugin.
*
* @public
- *
- * @remarks
- *
- * The `moduleId` should be equal to the module-specific suffix of the exported name, such
- * that the full name is `pluginId + "Module" + ModuleId`. For example, a GitHub entity
- * provider module for the `catalog` plugin might have the module ID `'githubEntityProvider'`,
- * and the full exported name would be `catalogModuleGithubEntityProvider`.
- *
- * The `pluginId` should exactly match the `id` of the plugin that the module extends.
+ * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules}
+ * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
export function createBackendModule(
config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
From 120594f05eb7b965dcf2c35dcedebb76cc5e5569 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 14:46:04 +0100
Subject: [PATCH 044/156] amend the threat model docs regarding package bumps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
docs/overview/threat-model.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md
index 57fafd1c28..eddb7539f8 100644
--- a/docs/overview/threat-model.md
+++ b/docs/overview/threat-model.md
@@ -28,6 +28,8 @@ Other responsibilities include protecting the integrity of configuration files a
The integrator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source.
+The integrator is also responsible for due diligence in maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as `dependabot` on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
+
## Common Backend Configuration
There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `TaskScheduler` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected.
From 219debde712fefead96be62a041761e505210407 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 14:48:16 +0100
Subject: [PATCH 045/156] better links
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
docs/overview/threat-model.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md
index eddb7539f8..b18f21bd37 100644
--- a/docs/overview/threat-model.md
+++ b/docs/overview/threat-model.md
@@ -28,7 +28,7 @@ Other responsibilities include protecting the integrity of configuration files a
The integrator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source.
-The integrator is also responsible for due diligence in maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as `dependabot` on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
+The integrator is also responsible for due diligence in maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
## Common Backend Configuration
From db887c4884c836d155b8bdb862ca64ed29c61d6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 14:50:41 +0100
Subject: [PATCH 046/156] less diligence
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
docs/overview/threat-model.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md
index b18f21bd37..a6435f8cc5 100644
--- a/docs/overview/threat-model.md
+++ b/docs/overview/threat-model.md
@@ -28,7 +28,7 @@ Other responsibilities include protecting the integrity of configuration files a
The integrator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source.
-The integrator is also responsible for due diligence in maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
+The integrator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
## Common Backend Configuration
From 4930413406fa05d07f9ea923c8a95b14580ec3be Mon Sep 17 00:00:00 2001
From: Dominik Pfaffenbauer
Date: Wed, 25 Jan 2023 14:58:49 +0100
Subject: [PATCH 047/156] Gitlab Org Provider changes
Signed-off-by: Dominik Pfaffenbauer
---
docs/integrations/gitlab/org.md | 5 +++
.../src/lib/client.ts | 41 +++++++++++++++----
.../src/lib/types.ts | 4 +-
.../GitlabOrgDiscoveryEntityProvider.ts | 17 ++++++--
4 files changed, 53 insertions(+), 14 deletions(-)
diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md
index caf0851477..fc87f968d1 100644
--- a/docs/integrations/gitlab/org.md
+++ b/docs/integrations/gitlab/org.md
@@ -19,6 +19,11 @@ integrations:
token: ${GITLAB_TOKEN}
```
+This will query all users and groups from your gitlab installation. Depending on the size
+of the Gitlab Instance, this can take some time our resources.
+
+The token that is used for the Organization Integration, has to be an Admin PAT.
+
```yaml
catalog:
providers:
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
index b86de7bf1d..bad0eb2a86 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
@@ -22,14 +22,22 @@ import {
import { Logger } from 'winston';
import { GitLabGroup, GitLabMembership, GitLabUser } from './types';
-export type ListOptions = {
+export type CommonListOptions = {
[key: string]: string | number | boolean | undefined;
- group?: string;
per_page?: number | undefined;
page?: number | undefined;
active?: boolean;
};
+interface ListProjectOptions extends CommonListOptions {
+ group?: string;
+}
+
+interface UserListOptions extends CommonListOptions {
+ without_project_bots?: boolean | undefined;
+ exclude_internal?: boolean | undefined;
+}
+
export type PagedResponse = {
items: T[];
nextPage?: number;
@@ -51,7 +59,9 @@ export class GitLabClient {
return this.config.host !== 'gitlab.com';
}
- async listProjects(options?: ListOptions): Promise> {
+ async listProjects(
+ options?: ListProjectOptions,
+ ): Promise> {
if (options?.group) {
return this.pagedRequest(
`/groups/${encodeURIComponent(options?.group)}/projects`,
@@ -65,11 +75,24 @@ export class GitLabClient {
return this.pagedRequest(`/projects`, options);
}
- async listUsers(options?: ListOptions): Promise> {
- return this.pagedRequest(`/users`, options);
+ async listUsers(
+ options?: UserListOptions,
+ ): Promise> {
+ let requestOptions = options;
+
+ if (!requestOptions) {
+ requestOptions = {};
+ }
+
+ requestOptions.without_project_bots = true;
+ requestOptions.exclude_internal = true;
+
+ return this.pagedRequest(`/users?`, requestOptions);
}
- async listGroups(options?: ListOptions): Promise> {
+ async listGroups(
+ options?: CommonListOptions,
+ ): Promise> {
return this.pagedRequest(`/groups`, options);
}
@@ -150,7 +173,7 @@ export class GitLabClient {
*/
async pagedRequest(
endpoint: string,
- options?: ListOptions,
+ options?: CommonListOptions,
): Promise> {
const request = new URL(`${this.config.apiBaseUrl}${endpoint}`);
for (const key in options) {
@@ -195,8 +218,8 @@ export class GitLabClient {
* @param options - Initial ListOptions for the request function.
*/
export async function* paginated(
- request: (options: ListOptions) => Promise>,
- options: ListOptions,
+ request: (options: CommonListOptions) => Promise>,
+ options: CommonListOptions,
) {
let res;
do {
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts
index 97ea87ea98..2b2b888851 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts
@@ -34,9 +34,9 @@ export type GitLabProject = {
export type GitLabUser = {
id: number;
username: string;
- name: string;
email: string;
- active: boolean;
+ name: string;
+ state: string;
web_url: string;
avatar_url: string;
groups?: GitLabGroup[];
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
index df480de3cb..a66efc1369 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts
@@ -206,13 +206,13 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
}
for await (const user of users) {
- if (!this.config.userPattern.test(user.email ?? '')) {
+ if (!this.config.userPattern.test(user.email ?? user.username ?? '')) {
continue;
}
res.scanned++;
- if (user.active) {
+ if (user.state !== 'active') {
continue;
}
@@ -314,7 +314,6 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
},
spec: {
profile: {
- email: user.email,
displayName: user.name,
picture: user.avatar_url,
},
@@ -322,6 +321,18 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
},
};
+ if (user.email) {
+ if (!entity.spec) {
+ entity.spec = {};
+ }
+
+ if (!entity.spec.profile) {
+ entity.spec.profile = {};
+ }
+
+ entity.spec.profile.email = user.email;
+ }
+
if (user.groups) {
for (const group of user.groups) {
if (!entity.spec.memberOf) {
From 3dad2bfdffc57d5ee033086cf44b413846e90d34 Mon Sep 17 00:00:00 2001
From: Giorgos Papaefthimiou
Date: Wed, 25 Jan 2023 16:06:12 +0200
Subject: [PATCH 048/156] Update org.md
`luxon` dependency is not used
Signed-off-by: Giorgos Papaefthimiou
---
docs/integrations/ldap/org.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md
index dbd3f4ff43..f8b9a18c55 100644
--- a/docs/integrations/ldap/org.md
+++ b/docs/integrations/ldap/org.md
@@ -37,7 +37,6 @@ schedule it:
```diff
// packages/backend/src/plugins/catalog.ts
-+import { Duration } from 'luxon';
+import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
export default async function createPlugin(
From 83b719678c976144bb8937048301e7c555d9ab09 Mon Sep 17 00:00:00 2001
From: Patrik Oldsberg
Date: Wed, 25 Jan 2023 16:04:45 +0100
Subject: [PATCH 049/156] docs/backend-system: service architecture
Signed-off-by: Patrik Oldsberg
---
.../architecture/03-services.md | 278 +++++++++++++-----
1 file changed, 201 insertions(+), 77 deletions(-)
diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md
index 27fea27db9..21fb085c1d 100644
--- a/docs/backend-system/architecture/03-services.md
+++ b/docs/backend-system/architecture/03-services.md
@@ -1,103 +1,227 @@
---
id: services
-title: Backend Service APIs
-sidebar_label: Service APIs
+title: Backend Services
+sidebar_label: Services
# prettier-ignore
-description: Service APIs for backend plugins
+description: Services for backend plugins
---
-## Backend Services
+Backend services provide shared functionality available to all backend plugins and modules. They are made available through services references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference.
-The default backend provides several [core services](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/coreServices.ts) out of the box which includes access to configuration, logging, databases and more.
-Service dependencies are declared using their `ServiceRef`s in the `deps` section of the plugin or module, and the implementations are then forwarded to the `init` method of the plugin or module.
+The system surrounding services exists to provide a level of indirection between the service interfaces and their implementation. It is an implementation of dependency injection, where each backend instance is the dependency injection container. The implementation for each service is provided by a service factory, which encapsulates the logic for how each service instance is created.
-### Service References
+## Service Interfaces
-A `ServiceRef` is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend.
-Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database.
+Service interfaces can be any TypeScript type, but it is best make it an object interface with a number of methods. General guidelines for interface design apply, keep them simple and lean, with few but powerful methods. Take care to avoid locking down the ways in which individual methods can evolve. Often you want to stick to a method with an options object as its only parameter, and return a result object. If there is any reason on uncertainty whether the method should be async or not, always make it async. For example, a minimal interface should often use the following pattern:
-On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them.
-ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance.
+```ts
+export interface FooService {
+ foo(options: FooOptions): Promise;
+}
+```
-#### Defining a Service
+## Service References
+
+Once you have defined a service interface, you need to create a service reference using the `createServiceRef` function. This will create a `ServiceRef` instance, which is a reference that you export in order to allow users to interact with your service. Conceptually this is very similar to `ApiRef`s in the frontend system. For example:
+
+```ts
+import { createServiceRef } from '@backstage/backend-plugin-api';
+
+export interface FooService {
+ foo(options: FooOptions): Promise;
+}
+
+export const fooServiceRef = createServiceRef({
+ id: 'example.foo', // the owner of this service is in this case the 'example' plugin
+});
+```
+
+The `fooServiceRef` that we create above should be exported, and can then be used to declare a dependency on the `FooService` interface and receive and implementation of it at runtime.
+
+When creating a service reference you need to give it an ID. This ID needs to be globally unique, and should generally be of the format `'.'`. For more naming patters surrounding services, see the [naming patterns](./07-naming-patterns.md#services) page.
+
+A note on naming: the frontend and backend systems intentionally use the separate names "APIs" and "Services" for concepts that are quite similar. This is to avoid confusion between the two, both in documentation and discussion, but also in code. While the two systems are quite similar, they are not identical, and they can't be used interchangeably.
+
+## Service Factories
+
+In order to be able to depend on a service interface through a service reference, we of course also need to have some way of creating the concrete implementation of it. To encapsulate that logic we use service factories, which define both how service instances are created, as well as what other services they depends on for their implementation.
+
+Service factories can come from many different sources. There are built-in service factories, external ones that you can import from other packages, and you can also create your own. Specific service factories are installed within each backend instance, which acts as the dependency injection container. For any given backend instance there can only be a single designated service factory for each service.
+
+To define a service factory, we use `createServiceFactory`:
+
+```ts
+import { createServiceFactory } from '@backstage/backend-plugin-api';
+
+class DefaultFooService implements FooService {
+ async foo(options: FooOptions): Promise {
+ // ...
+ }
+}
+
+export const fooFactory = createServiceFactory({
+ service: fooServiceRef,
+ deps: { bar: barServiceRef },
+ factory({ bar }) {
+ return new DefaultFooService(bar);
+ },
+});
+```
+
+To create a service factory we need to provide a reference to the `service` for which the factory will create instances, a `deps` object which lists the other services that the factory depends on, and a `factory` function which will be called to create the service instance. The backend system will call the `factory` function with an object that contains the service instances for each of the dependencies listed in the `deps` object. If a service implementation does not depend on any other services, the `deps` are left as an empty object (`{}`). The `factory` function must return a value that implements the service interface.
+
+If you need the creation of the service instance to be asynchronous, you can make the `factory` function async. For example:
+
+```ts
+export const fooFactory = createServiceFactory({
+ service: fooServiceRef,
+ deps: {},
+ async factory() {
+ const foo = new DefaultFooService();
+ await foo.init();
+ return foo;
+ },
+});
+```
+
+Note that circular dependencies among service factories are not allowed. This is verified at runtime, and your backend instance will refuse to start up it detects any conflicts. Likewise, the backend will also fail to start up if a service factory depends on a service that is not provided by any registered service factory.
+
+## Service Factory Options
+
+To install a service factory in a backend instance, we pass it in through the `services` option to `createBackend`:
+
+```ts
+const backend = createBackend({
+ services: [fooFactory()],
+});
+```
+
+Note that we call `fooFactory` to create the service factory instance. This is because `createServiceFactory` always returns a factory function that creates the actual service factory. This is done to always allow for options to be added to the service factory in the future, without breaking existing code. To add options to your service factory, you wrap the object passed to `createServiceFactory` in a callback that accepts the desired options. For example:
+
+```ts
+export interface FooFactoryOptions {
+ mode: 'eager' | 'lazy';
+}
+
+export const fooFactory = createServiceFactory(
+ (options?: FooFactoryOptions) => ({
+ service: fooServiceRef,
+ deps: { bar: barServiceRef },
+ factory({ bar }) {
+ return new DefaultFooService(bar, options?.mode);
+ },
+ }),
+);
+```
+
+This lets us use the options to customize the factory implementation in any way we want. From the outside the service factory looks just like before, except that we're now also able to pass options when installing the factory:
+
+```ts
+const backend = createBackend({
+ services: [fooFactory({ mode: 'eager' })],
+});
+```
+
+## Core Services
+
+The backend system provides a number of core service definitions that both help implement the main functionality of the backend, but also provide a set of utilities for common concerns, such as logging, database access, job scheduling, and so on. These core services will always be present in a backend instance created with `createBackend`, and they can all be overridden with custom implementations if needed.
+
+The service references for all core services are exported via their own `coreServices` object, available from the `@backstage/backend-plugin-api` package. For example, the logging service is accessible via `coreServices.logger`.
+
+You can read more about what core services there are and how to use them in the [core services](../core-services/01-index.md) section.
+
+## Service Scope
+
+By default services are scoped to individual plugins, meaning that separate instances of the service will be created for each plugin. For example, in our `fooFactory` above, a separate instance of `DefaultFooService` will be created for every plugin that depends on the service. This both makes it possible to tailor the service implementations for the individual plugins, and also ensures some level of separation between plugins.
+
+The service scope is defined during the call to `createServiceRef`, with plugin scope being the default. Our above definition of the `fooServiceRef` is therefore equivalent to the following:
+
+```ts
+export const fooServiceRef = createServiceRef({
+ scope: 'plugin',
+ id: 'example.foo',
+});
+```
+
+There are only two possible scopes for services, `'plugin'` and `'root'`.
+
+## Root Scoped Services
+
+If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugin depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
+
+There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it.
+
+Because of these limitations and particular use-cases for root scoped services, they tend to be more rare than plugin scoped services. In general, you should prefer defining a service as plugin scoped, unless you are implementing either of the two mentioned use-cases.
+
+Some services come in pairs of a plugin and a root scoped service definition. For example, the `rootLogger` service is a root scoped service, while the `logger` service is a plugin scoped service. The `rootLogger` service houses the main logging implementation, while the `logger` service simply builds upon the `rootLogger` to add plugin specific labels. This division exists so that other root scoped services also have access to a logging service, but it is always preferable if the split can be avoided. If you do end up implementing this pattern, the root scoped service should be prefixed with `root`, this is to encourage use of the plugin scoped service instead.
+
+## Plugin Metadata
+
+Plugins scoped services have access to a plugin metadata service, which is a special service provided by the backend system that is not possible to override. The plugin metadata service provides information about the plugin that a service instance is being created for. It is itself a plugin scoped service, and can be depended on like any other service through the `coreServices.pluginMetadata` reference.
+
+The plugin metadata service is the base for all plugin specific customizations for services. For example, the default implementation of the plugin scoped logger service uses the plugin metadata service to attach the plugin ID as a field in all log messages:
+
+```ts
+export const loggerFactory = createServiceFactory({
+ service: coreServices.logger,
+ deps: {
+ rootLogger: coreServices.rootLogger,
+ pluginMetadata: coreServices.pluginMetadata,
+ },
+ factory({ rootLogger, pluginMetadata }) {
+ return rootLogger.child({ plugin: pluginMetadata.getId() });
+ },
+});
+```
+
+## Root Context for Service Factories
+
+Some service may benefit from having a context that is shared across all instances of a service. This of course only applies to plugin scoped services, as root scoped services only ever have a single instance. The root context could for example be used for sharing a common connection pool for database access, generated secrets for development, or any other kind of shared facility. Note that you should not use this to share state between plugins in production, as that would be a violation of the [plugin isolation rule](./04-plugins.md#rules-of-plugins).
+
+The root context is defined as part of the service factory by passing the `createRootContext` option:
+
+```ts
+export const fooFactory = createServiceFactory({
+ service: fooServiceRef,
+ deps: { rootLogger: coreServices.rootLogger, bar: barServiceRef },
+ createRootContext({ rootLogger }) {
+ return new FooRootContext(rootLogger);
+ }
+ factory({ bar }, ctx) {
+ return ctx.forPlugin(bar)
+ },
+});
+```
+
+Whatever value is returned by the `createRootContext` function will shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`.
+
+## Default Service Factories
+
+There are a lot of services that are installed in any standard Backstage backend instance by default. You can expect on these services to always exist, and do not need to take any additional steps to make them available. This is not necessarily true for services that you import from external packages, as the user of your plugin or module might not have installed a factory for that service in their backend. In order to avoid having to ask integrators of your plugin to install a service factory for a service that you depend on, it is possible to define a default factory for a service.
+
+Default service factories are defined as part of the service reference by passing the `defaultFactory` option to `createServiceRef`:
```ts
import {
createServiceFactory,
- coreServices,
+ createServiceRef,
} from '@backstage/backend-plugin-api';
-import { ExampleImpl } from './ExampleImpl';
-export interface ExampleApi {
- doSomething(): Promise;
-}
-
-export const exampleServiceRef = createServiceRef({
- id: 'example',
- scope: 'plugin', // can be 'root' or 'plugin'
-
- // The defaultFactory is optional to implement but it will be used if no
- // other factory is provided to the backend. This allows for the backend
- // to provide a default implementation of the service without having to wire
- // it beforehand.
+export const fooServiceRef = createServiceRef({
+ id: 'example.foo',
defaultFactory: async service =>
createServiceFactory({
service,
- deps: {
- logger: coreServices.logger,
- plugin: coreServices.pluginMetadata,
- },
- // This root context method is only available for plugin scoped services.
- // It's only called once per backend instance.
- // Logger is available at the root context level as it's also a root
- // scoped service.
- createRootContext({ logger }) {
- return new ExampleImplFactory({ logger });
- },
- // Plugin is available as it's a plugin scoped service and will be
- // created once per plugin. The logger can be had here too if needed.
- // Both this anc the root context can also be async.
- factory({ logger, plugin }, rootContext) {
- // This block will be executed once for every plugin that depends on
- // this service
- logger.info('Initializing example service plugin instance');
- return rootContext.forPlugin(plugin.getId());
+ deps: {},
+ factory() {
+ return new DefaultFooService();
},
}),
});
```
-### Overriding Services
+Note that we don't use the `fooServiceRef` when creating our service factory, but instead use the `service` parameter in the default factory callback. This is because attempting to use `fooServiceRef` directly would result in a circular reference.
-In this example we replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service.
+If a service defines a default factory, that factory will be used if there is no explicit factory registered in the backend for that service. This allows users of your service to directly import and use a service, without worrying about whether it is installed or not. It is recommended to always define a default factory for any service that you are exporting for use in other plugins or modules.
-```ts
-import {
- createServiceFactory,
- rootLoggerServiceRef,
- LoggerService,
-} from '@backstage/backend-plugin-api';
-
-// This custom implementation would typically live separately from
-// the backend setup code, either nearby such as in
-// packages/backend/src/services/logger/GoogleCloudLogger.ts
-// Or you can let it live in its own library package.
-class GoogleCloudLogger implements LoggerService {
- static factory = createServiceFactory({
- service: rootLoggerServiceRef,
- deps: {},
- factory() {
- return new GoogleCloudLogger();
- },
- });
- // custom implementation here ...
-}
-
-// packages/backend/src/index.ts
-const backend = createBackend({
- services: [
- // supplies additional or replacement services to the backend
- GoogleCloudLogger.factory(),
- ],
-});
-```
+When defining a default factory for a service, it is possible for it to end up with duplicate implementations at runtime. This applies both to any shared root context in your factory, as well as plugin specific instances of your service. This is because package dependency version ranges may not line up perfectly, causing duplicate installations of the same package. This can happen both for two different plugins using the same service, but also across a plugin and its modules. If your service would break in this scenario, you should not define a default factory for it, but instead require that users of your service explicitly install a factory in their backend instance.
From b40661378d5318c61748423ce17df475384e75aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 16:47:14 +0100
Subject: [PATCH 050/156] add a test for the core scheduler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
packages/backend-app-api/package.json | 1 +
.../scheduler/schedulerFactory.test.ts | 68 +++++++++++++++++++
2 files changed, 69 insertions(+)
create mode 100644 packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index f7c574d7b6..ef34fbdb25 100644
--- a/packages/backend-app-api/package.json
+++ b/packages/backend-app-api/package.json
@@ -64,6 +64,7 @@
"winston-transport": "^4.5.0"
},
"devDependencies": {
+ "@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/compression": "^1.7.0",
"@types/fs-extra": "^9.0.3",
diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts
new file mode 100644
index 0000000000..1aaf4ec71d
--- /dev/null
+++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { startTestBackend } from '@backstage/backend-test-utils';
+import { schedulerFactory } from './schedulerFactory';
+
+describe('schedulerFactory', () => {
+ it('creates sidecar database features', async () => {
+ expect.assertions(3);
+
+ const subject = schedulerFactory();
+
+ const plugin = createBackendPlugin({
+ id: 'example',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ scheduler: subject.service,
+ database: coreServices.database,
+ },
+ init: async ({ scheduler, database }) => {
+ await scheduler.scheduleTask({
+ id: 'task1',
+ timeout: { seconds: 1 },
+ frequency: { seconds: 1 },
+ fn: async () => {},
+ });
+
+ const client = await database.getClient();
+ await expect(
+ client.from('backstage_backend_tasks__tasks').count(),
+ ).resolves.toEqual([{ 'count(*)': 1 }]);
+ await expect(
+ client.from('backstage_backend_tasks__knex_migrations').count(),
+ ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
+ await expect(
+ client
+ .from('backstage_backend_tasks__knex_migrations_lock')
+ .count(),
+ ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
+ },
+ });
+ },
+ });
+
+ await startTestBackend({
+ features: [plugin()],
+ services: [subject],
+ });
+ });
+});
From 8c2468e56921c8218210b7b5efc4b0dad72b8745 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 16:56:14 +0100
Subject: [PATCH 051/156] yarn lock too
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
yarn.lock | 1 +
1 file changed, 1 insertion(+)
diff --git a/yarn.lock b/yarn.lock
index ede8ab22a2..a6c98b23bf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3387,6 +3387,7 @@ __metadata:
"@backstage/backend-common": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-tasks": "workspace:^"
+ "@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/cli-common": "workspace:^"
"@backstage/config": "workspace:^"
From 476d5a83c77572ef4ffc35938769c9e837bb2319 Mon Sep 17 00:00:00 2001
From: Patrik Oldsberg
Date: Wed, 25 Jan 2023 17:36:24 +0100
Subject: [PATCH 052/156] Apply suggestions from code review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Fredrik Adelöw
Co-authored-by: Phil Kuang
Signed-off-by: Patrik Oldsberg
---
.../backend-system/architecture/03-services.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md
index 21fb085c1d..96b82d7e95 100644
--- a/docs/backend-system/architecture/03-services.md
+++ b/docs/backend-system/architecture/03-services.md
@@ -6,13 +6,13 @@ sidebar_label: Services
description: Services for backend plugins
---
-Backend services provide shared functionality available to all backend plugins and modules. They are made available through services references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference.
+Backend services provide shared functionality available to all backend plugins and modules. They are made available through service references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference.
The system surrounding services exists to provide a level of indirection between the service interfaces and their implementation. It is an implementation of dependency injection, where each backend instance is the dependency injection container. The implementation for each service is provided by a service factory, which encapsulates the logic for how each service instance is created.
## Service Interfaces
-Service interfaces can be any TypeScript type, but it is best make it an object interface with a number of methods. General guidelines for interface design apply, keep them simple and lean, with few but powerful methods. Take care to avoid locking down the ways in which individual methods can evolve. Often you want to stick to a method with an options object as its only parameter, and return a result object. If there is any reason on uncertainty whether the method should be async or not, always make it async. For example, a minimal interface should often use the following pattern:
+Service interfaces can be any TypeScript type, but it is best to make it an object interface with a number of methods. General guidelines for interface design apply: keep them simple and lean, with few but powerful methods. Take care to avoid locking down the ways in which individual methods can evolve. Often you want to stick to a method with an options object as its only parameter, and return a result object. If there is any reason for uncertainty about whether the method should be async or not, always make it async. For example, a minimal interface should often use the following pattern:
```ts
export interface FooService {
@@ -36,7 +36,7 @@ export const fooServiceRef = createServiceRef({
});
```
-The `fooServiceRef` that we create above should be exported, and can then be used to declare a dependency on the `FooService` interface and receive and implementation of it at runtime.
+The `fooServiceRef` that we create above should be exported, and can then be used to declare a dependency on the `FooService` interface and receive an implementation of it at runtime.
When creating a service reference you need to give it an ID. This ID needs to be globally unique, and should generally be of the format `'.'`. For more naming patters surrounding services, see the [naming patterns](./07-naming-patterns.md#services) page.
@@ -44,7 +44,7 @@ A note on naming: the frontend and backend systems intentionally use the separat
## Service Factories
-In order to be able to depend on a service interface through a service reference, we of course also need to have some way of creating the concrete implementation of it. To encapsulate that logic we use service factories, which define both how service instances are created, as well as what other services they depends on for their implementation.
+In order to be able to depend on a service interface through a service reference, we of course also need to have some way of creating the concrete implementation of it. To encapsulate that logic we use service factories, which define both how service instances are created, as well as what other services they depend on for their implementation.
Service factories can come from many different sources. There are built-in service factories, external ones that you can import from other packages, and you can also create your own. Specific service factories are installed within each backend instance, which acts as the dependency injection container. For any given backend instance there can only be a single designated service factory for each service.
@@ -84,7 +84,7 @@ export const fooFactory = createServiceFactory({
});
```
-Note that circular dependencies among service factories are not allowed. This is verified at runtime, and your backend instance will refuse to start up it detects any conflicts. Likewise, the backend will also fail to start up if a service factory depends on a service that is not provided by any registered service factory.
+Note that circular dependencies among service factories are not allowed. This is verified at runtime, and your backend instance will refuse to start up if it detects any conflicts. Likewise, the backend will also fail to start up if a service factory depends on a service that is not provided by any registered service factory.
## Service Factory Options
@@ -147,7 +147,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`.
## Root Scoped Services
-If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugin depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
+If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it.
@@ -157,7 +157,7 @@ Some services come in pairs of a plugin and a root scoped service definition. Fo
## Plugin Metadata
-Plugins scoped services have access to a plugin metadata service, which is a special service provided by the backend system that is not possible to override. The plugin metadata service provides information about the plugin that a service instance is being created for. It is itself a plugin scoped service, and can be depended on like any other service through the `coreServices.pluginMetadata` reference.
+Plugin scoped services have access to a plugin metadata service, which is a special service provided by the backend system that is not possible to override. The plugin metadata service provides information about the plugin that a service instance is being created for. It is itself a plugin scoped service, and can be depended on like any other service through the `coreServices.pluginMetadata` reference.
The plugin metadata service is the base for all plugin specific customizations for services. For example, the default implementation of the plugin scoped logger service uses the plugin metadata service to attach the plugin ID as a field in all log messages:
@@ -176,7 +176,7 @@ export const loggerFactory = createServiceFactory({
## Root Context for Service Factories
-Some service may benefit from having a context that is shared across all instances of a service. This of course only applies to plugin scoped services, as root scoped services only ever have a single instance. The root context could for example be used for sharing a common connection pool for database access, generated secrets for development, or any other kind of shared facility. Note that you should not use this to share state between plugins in production, as that would be a violation of the [plugin isolation rule](./04-plugins.md#rules-of-plugins).
+Some services may benefit from having a context that is shared across all instances of a service. This of course only applies to plugin scoped services, as root scoped services only ever have a single instance. The root context could for example be used for sharing a common connection pool for database access, generated secrets for development, or any other kind of shared facility. Note that you should not use this to share state between plugins in production, as that would be a violation of the [plugin isolation rule](./04-plugins.md#rules-of-plugins).
The root context is defined as part of the service factory by passing the `createRootContext` option:
@@ -197,7 +197,7 @@ Whatever value is returned by the `createRootContext` function will shared and p
## Default Service Factories
-There are a lot of services that are installed in any standard Backstage backend instance by default. You can expect on these services to always exist, and do not need to take any additional steps to make them available. This is not necessarily true for services that you import from external packages, as the user of your plugin or module might not have installed a factory for that service in their backend. In order to avoid having to ask integrators of your plugin to install a service factory for a service that you depend on, it is possible to define a default factory for a service.
+There are a lot of services that are installed in any standard Backstage backend instance by default. You can expect these services to always exist, and do not need to take any additional steps to make them available. This is not necessarily true for services that you import from external packages, as the user of your plugin or module might not have installed a factory for that service in their backend. In order to avoid having to ask integrators of your plugin to install a service factory for a service that you depend on, it is possible to define a default factory for a service.
Default service factories are defined as part of the service reference by passing the `defaultFactory` option to `createServiceRef`:
From e448b1a1e9580d302270e2902cfbc4dfd62a5a87 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 25 Jan 2023 16:40:51 +0000
Subject: [PATCH 053/156] fix(deps): update dependency
@apidevtools/json-schema-ref-parser to v9.1.2
Signed-off-by: Renovate Bot
---
yarn.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index ede8ab22a2..8b63950b77 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -22,14 +22,14 @@ __metadata:
linkType: hard
"@apidevtools/json-schema-ref-parser@npm:^9.0.6":
- version: 9.1.1
- resolution: "@apidevtools/json-schema-ref-parser@npm:9.1.1"
+ version: 9.1.2
+ resolution: "@apidevtools/json-schema-ref-parser@npm:9.1.2"
dependencies:
"@jsdevtools/ono": ^7.1.3
"@types/json-schema": ^7.0.6
call-me-maybe: ^1.0.1
js-yaml: ^4.1.0
- checksum: f8fb98fec4d510ef7a135256745b7321fbc1547a83cdeb458b143d46f3d5fae1450673a9f485f77051391d052e6ec9d6ec6c0d3091d7dfe4b6ac5d85ee77e10d
+ checksum: 5bd6885db0fd6633879bb4638b7a3aead6b061cb6422083c6be505ee6873be54e3376380df164c73edd8901d4145a9bfe9bc0b008a568fd8b0626b1df96fae8f
languageName: node
linkType: hard
From aeac2d531f4094a326865efdae6433dd710994a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Wed, 25 Jan 2023 18:07:02 +0100
Subject: [PATCH 054/156] lower to patch level
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
.changeset/sharp-lobsters-build.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/sharp-lobsters-build.md b/.changeset/sharp-lobsters-build.md
index f9d317a1f6..d00333581b 100644
--- a/.changeset/sharp-lobsters-build.md
+++ b/.changeset/sharp-lobsters-build.md
@@ -1,5 +1,5 @@
---
-'@backstage/backend-plugin-api': minor
+'@backstage/backend-plugin-api': patch
---
The `register` methods passed to `createBackendPlugin` and `createBackendModule`
From 15053c2a002c35802ca6d586aa9bd09e27303b91 Mon Sep 17 00:00:00 2001
From: Dmytro Dovbii
Date: Wed, 25 Jan 2023 19:48:40 +0200
Subject: [PATCH 055/156] Add torque plugin
Signed-off-by: Dmytro Dovbii
---
microsite/data/plugins/torque.yaml | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 microsite/data/plugins/torque.yaml
diff --git a/microsite/data/plugins/torque.yaml b/microsite/data/plugins/torque.yaml
new file mode 100644
index 0000000000..1480c3bad9
--- /dev/null
+++ b/microsite/data/plugins/torque.yaml
@@ -0,0 +1,12 @@
+---
+title: Torque
+author: Quali
+authorUrl: 'https://www.qtorque.io/'
+category: Orchestration
+description: |
+ Deploy the environments you need to keep building, testing and delivering incredible code.
+ Plugin includes an Entity ComponentCard, Backend API route and scaffolder actions.
+documentation: https://github.com/QualiTorque/torque-backstage-plugin/tree/main/packages/torque#readme
+iconUrl: https://user-images.githubusercontent.com/8643801/214640977-751bc338-6b77-40a0-a897-cba41b6f006a.png
+npmPackageName: '@qtorque/backstage-plugin-torque'
+addedDate: '2023-01-25'
From 06605d38f082b944103fe0dde50ce0cd9368dc5a Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 25 Jan 2023 17:56:27 +0000
Subject: [PATCH 056/156] fix(deps): update dependency openid-client to v5.3.2
Signed-off-by: Renovate Bot
---
yarn.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index e8a5742e8c..f8dcb7bab5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -30652,14 +30652,14 @@ __metadata:
linkType: hard
"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0":
- version: 5.3.1
- resolution: "openid-client@npm:5.3.1"
+ version: 5.3.2
+ resolution: "openid-client@npm:5.3.2"
dependencies:
jose: ^4.10.0
lru-cache: ^6.0.0
object-hash: ^2.0.1
oidc-token-hash: ^5.0.1
- checksum: e8993cfe6ac55942fb7a5fc4db8ab88ab36bbcf2a6519993a9a2e288bfc248fcfb07804b6785eb7852a9eea0eec27f80ae57a29193c0b835f31f611b11b9bda1
+ checksum: 59dc58d5b5cb795f3500f2d9d7ce604f6fb4c00623b16660fdf635d2789c3b2bb32e328ff10c918f9fa4e7fb269c768658d00769761d301be2d96d6150e07051
languageName: node
linkType: hard
From 434dc117a14ba30535eb79674b863a1ef4c20c3d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 25 Jan 2023 17:56:59 +0000
Subject: [PATCH 057/156] fix(deps): update dependency react-markdown to v8.0.5
Signed-off-by: Renovate Bot
---
yarn.lock | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index e8a5742e8c..1c50046ad0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -33230,8 +33230,8 @@ __metadata:
linkType: hard
"react-markdown@npm:^8.0.0":
- version: 8.0.4
- resolution: "react-markdown@npm:8.0.4"
+ version: 8.0.5
+ resolution: "react-markdown@npm:8.0.5"
dependencies:
"@types/hast": ^2.0.0
"@types/prop-types": ^15.0.0
@@ -33244,14 +33244,14 @@ __metadata:
remark-parse: ^10.0.0
remark-rehype: ^10.0.0
space-separated-tokens: ^2.0.0
- style-to-object: ^0.3.0
+ style-to-object: ^0.4.0
unified: ^10.0.0
unist-util-visit: ^4.0.0
vfile: ^5.0.0
peerDependencies:
"@types/react": ">=16"
react: ">=16"
- checksum: 9feec3734694ef05f450779a1adac10dbb768b8f3adbf4c81f59ca3cea8fd2f6a578fa4c488183597a7418f972715e5d84705cc76f278b55c86d622e82561bf8
+ checksum: 9d11b7aba16216d590e56b4744e05d2925141bfb0f5885b3d9400ccf006cd24b79ce3b3d20af8a083a01324215b58fa4c5979e44f69d54123ff1dd5dacb0dc89
languageName: node
linkType: hard
@@ -36207,12 +36207,12 @@ __metadata:
languageName: node
linkType: hard
-"style-to-object@npm:^0.3.0":
- version: 0.3.0
- resolution: "style-to-object@npm:0.3.0"
+"style-to-object@npm:^0.4.0":
+ version: 0.4.1
+ resolution: "style-to-object@npm:0.4.1"
dependencies:
inline-style-parser: 0.1.1
- checksum: 4d7084015207f2a606dfc10c29cb5ba569f2fe8005551df7396110dd694d6ff650f2debafa95bd5d147dfb4ca50f57868e2a7f91bf5d11ef734fe7ccbd7abf59
+ checksum: 2ea213e98eed21764ae1d1dc9359231a9f2d480d6ba55344c4c15eb275f0809f1845786e66d4caf62414a5cc8f112ce9425a58d251c77224060373e0db48f8c2
languageName: node
linkType: hard
From 66cf22fdc41e915dd36503b8b7d7332e27d2dfd8 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 25 Jan 2023 18:53:29 +0000
Subject: [PATCH 058/156] chore(deps): update dependency esbuild to ^0.17.0
Signed-off-by: Renovate Bot
---
.changeset/renovate-0c3644c.md | 6 +
packages/cli/package.json | 2 +-
plugins/scaffolder-backend/package.json | 2 +-
yarn.lock | 188 ++++++++++++------------
4 files changed, 102 insertions(+), 96 deletions(-)
create mode 100644 .changeset/renovate-0c3644c.md
diff --git a/.changeset/renovate-0c3644c.md b/.changeset/renovate-0c3644c.md
new file mode 100644
index 0000000000..c32438b452
--- /dev/null
+++ b/.changeset/renovate-0c3644c.md
@@ -0,0 +1,6 @@
+---
+'@backstage/cli': patch
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Updated dependency `esbuild` to `^0.17.0`.
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 486a64dcdb..59bb880ffb 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -67,7 +67,7 @@
"commander": "^9.1.0",
"css-loader": "^6.5.1",
"diff": "^5.0.0",
- "esbuild": "^0.16.0",
+ "esbuild": "^0.17.0",
"esbuild-loader": "^2.18.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index ad5edee668..6098fdc537 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -92,7 +92,7 @@
"@types/nunjucks": "^3.1.4",
"@types/supertest": "^2.0.8",
"@types/zen-observable": "^0.8.0",
- "esbuild": "^0.16.0",
+ "esbuild": "^0.17.0",
"jest-when": "^3.1.0",
"mock-fs": "^5.1.0",
"msw": "^0.49.0",
diff --git a/yarn.lock b/yarn.lock
index bcc1177fdc..fcfa45ff1a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3721,7 +3721,7 @@ __metadata:
css-loader: ^6.5.1
del: ^6.0.0
diff: ^5.0.0
- esbuild: ^0.16.0
+ esbuild: ^0.17.0
esbuild-loader: ^2.18.0
eslint: ^8.6.0
eslint-config-prettier: ^8.3.0
@@ -7282,7 +7282,7 @@ __metadata:
command-exists: ^1.2.9
compression: ^1.7.4
cors: ^2.8.5
- esbuild: ^0.16.0
+ esbuild: ^0.17.0
express: ^4.17.1
express-promise-router: ^4.1.0
fs-extra: 10.1.0
@@ -9058,9 +9058,9 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/android-arm64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/android-arm64@npm:0.16.16"
+"@esbuild/android-arm64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/android-arm64@npm:0.17.4"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
@@ -9072,65 +9072,65 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/android-arm@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/android-arm@npm:0.16.16"
+"@esbuild/android-arm@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/android-arm@npm:0.17.4"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
-"@esbuild/android-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/android-x64@npm:0.16.16"
+"@esbuild/android-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/android-x64@npm:0.17.4"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
-"@esbuild/darwin-arm64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/darwin-arm64@npm:0.16.16"
+"@esbuild/darwin-arm64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/darwin-arm64@npm:0.17.4"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/darwin-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/darwin-x64@npm:0.16.16"
+"@esbuild/darwin-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/darwin-x64@npm:0.17.4"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@esbuild/freebsd-arm64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/freebsd-arm64@npm:0.16.16"
+"@esbuild/freebsd-arm64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/freebsd-arm64@npm:0.17.4"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/freebsd-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/freebsd-x64@npm:0.16.16"
+"@esbuild/freebsd-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/freebsd-x64@npm:0.17.4"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/linux-arm64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-arm64@npm:0.16.16"
+"@esbuild/linux-arm64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-arm64@npm:0.17.4"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/linux-arm@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-arm@npm:0.16.16"
+"@esbuild/linux-arm@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-arm@npm:0.17.4"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@esbuild/linux-ia32@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-ia32@npm:0.16.16"
+"@esbuild/linux-ia32@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-ia32@npm:0.17.4"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
@@ -9142,86 +9142,86 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-loong64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-loong64@npm:0.16.16"
+"@esbuild/linux-loong64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-loong64@npm:0.17.4"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
-"@esbuild/linux-mips64el@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-mips64el@npm:0.16.16"
+"@esbuild/linux-mips64el@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-mips64el@npm:0.17.4"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
-"@esbuild/linux-ppc64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-ppc64@npm:0.16.16"
+"@esbuild/linux-ppc64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-ppc64@npm:0.17.4"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
-"@esbuild/linux-riscv64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-riscv64@npm:0.16.16"
+"@esbuild/linux-riscv64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-riscv64@npm:0.17.4"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
-"@esbuild/linux-s390x@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-s390x@npm:0.16.16"
+"@esbuild/linux-s390x@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-s390x@npm:0.17.4"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
-"@esbuild/linux-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/linux-x64@npm:0.16.16"
+"@esbuild/linux-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/linux-x64@npm:0.17.4"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
-"@esbuild/netbsd-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/netbsd-x64@npm:0.16.16"
+"@esbuild/netbsd-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/netbsd-x64@npm:0.17.4"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/openbsd-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/openbsd-x64@npm:0.16.16"
+"@esbuild/openbsd-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/openbsd-x64@npm:0.17.4"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/sunos-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/sunos-x64@npm:0.16.16"
+"@esbuild/sunos-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/sunos-x64@npm:0.17.4"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
-"@esbuild/win32-arm64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/win32-arm64@npm:0.16.16"
+"@esbuild/win32-arm64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/win32-arm64@npm:0.17.4"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/win32-ia32@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/win32-ia32@npm:0.16.16"
+"@esbuild/win32-ia32@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/win32-ia32@npm:0.17.4"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@esbuild/win32-x64@npm:0.16.16":
- version: 0.16.16
- resolution: "@esbuild/win32-x64@npm:0.16.16"
+"@esbuild/win32-x64@npm:0.17.4":
+ version: 0.17.4
+ resolution: "@esbuild/win32-x64@npm:0.17.4"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -21650,32 +21650,32 @@ __metadata:
languageName: node
linkType: hard
-"esbuild@npm:^0.16.0":
- version: 0.16.16
- resolution: "esbuild@npm:0.16.16"
+"esbuild@npm:^0.17.0":
+ version: 0.17.4
+ resolution: "esbuild@npm:0.17.4"
dependencies:
- "@esbuild/android-arm": 0.16.16
- "@esbuild/android-arm64": 0.16.16
- "@esbuild/android-x64": 0.16.16
- "@esbuild/darwin-arm64": 0.16.16
- "@esbuild/darwin-x64": 0.16.16
- "@esbuild/freebsd-arm64": 0.16.16
- "@esbuild/freebsd-x64": 0.16.16
- "@esbuild/linux-arm": 0.16.16
- "@esbuild/linux-arm64": 0.16.16
- "@esbuild/linux-ia32": 0.16.16
- "@esbuild/linux-loong64": 0.16.16
- "@esbuild/linux-mips64el": 0.16.16
- "@esbuild/linux-ppc64": 0.16.16
- "@esbuild/linux-riscv64": 0.16.16
- "@esbuild/linux-s390x": 0.16.16
- "@esbuild/linux-x64": 0.16.16
- "@esbuild/netbsd-x64": 0.16.16
- "@esbuild/openbsd-x64": 0.16.16
- "@esbuild/sunos-x64": 0.16.16
- "@esbuild/win32-arm64": 0.16.16
- "@esbuild/win32-ia32": 0.16.16
- "@esbuild/win32-x64": 0.16.16
+ "@esbuild/android-arm": 0.17.4
+ "@esbuild/android-arm64": 0.17.4
+ "@esbuild/android-x64": 0.17.4
+ "@esbuild/darwin-arm64": 0.17.4
+ "@esbuild/darwin-x64": 0.17.4
+ "@esbuild/freebsd-arm64": 0.17.4
+ "@esbuild/freebsd-x64": 0.17.4
+ "@esbuild/linux-arm": 0.17.4
+ "@esbuild/linux-arm64": 0.17.4
+ "@esbuild/linux-ia32": 0.17.4
+ "@esbuild/linux-loong64": 0.17.4
+ "@esbuild/linux-mips64el": 0.17.4
+ "@esbuild/linux-ppc64": 0.17.4
+ "@esbuild/linux-riscv64": 0.17.4
+ "@esbuild/linux-s390x": 0.17.4
+ "@esbuild/linux-x64": 0.17.4
+ "@esbuild/netbsd-x64": 0.17.4
+ "@esbuild/openbsd-x64": 0.17.4
+ "@esbuild/sunos-x64": 0.17.4
+ "@esbuild/win32-arm64": 0.17.4
+ "@esbuild/win32-ia32": 0.17.4
+ "@esbuild/win32-x64": 0.17.4
dependenciesMeta:
"@esbuild/android-arm":
optional: true
@@ -21723,7 +21723,7 @@ __metadata:
optional: true
bin:
esbuild: bin/esbuild
- checksum: d3163ec01e017776df6b68e1825caa2323918f0d03eb92250bdcdff80410a2c0eb5b3807955db84d83b1b91cf24af9815a1d19efc2343c490be3e5d7b27a834f
+ checksum: caedb2161cde7280d21a905098c3155224a06c9198b883fac085d0ff39dfc2f6dea01c71037e1409d0ef43323af5b022c74af245a29dac36d10c5af57db258b3
languageName: node
linkType: hard
From 4024b374495a8e9ad216fbc93dd2411b63249719 Mon Sep 17 00:00:00 2001
From: "Fernando.Teixeira"
Date: Wed, 25 Jan 2023 14:09:15 -0500
Subject: [PATCH 059/156] feat(tech-inights): adds getFactSchemas method to
client
Signed-off-by: Fernando.Teixeira
---
.changeset/four-candles-add.md | 18 ++++++
.../src/service/fact/FactRetrieverRegistry.ts | 2 +-
plugins/tech-insights-common/src/index.ts | 57 ++++++++++++++++++
plugins/tech-insights-node/src/facts.ts | 58 +------------------
plugins/tech-insights-node/src/persistence.ts | 2 +-
.../tech-insights/src/api/TechInsightsApi.ts | 2 +
.../src/api/TechInsightsClient.ts | 5 ++
7 files changed, 85 insertions(+), 59 deletions(-)
create mode 100644 .changeset/four-candles-add.md
diff --git a/.changeset/four-candles-add.md b/.changeset/four-candles-add.md
new file mode 100644
index 0000000000..bf465181b1
--- /dev/null
+++ b/.changeset/four-candles-add.md
@@ -0,0 +1,18 @@
+---
+'@backstage/plugin-tech-insights-backend': minor
+'@backstage/plugin-tech-insights-common': minor
+'@backstage/plugin-tech-insights-node': minor
+'@backstage/plugin-tech-insights': minor
+---
+
+TechInsightsApi interface now has getFactSchemas() method.
+TechInsightsClient now implements method getFactSchemas().
+
+**BREAKING** FactSchema type moved from @backstage/plugin-tech-insights-node into @backstage/plugin-tech-insights-common
+
+These changes are **required** if you were importing this type directly.
+
+```diff
+- import { FactSchema } from '@backstage/plugin-tech-insights-node';
++ import { FactSchema } from '@backstage/plugin-tech-insights-common';
+```
diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts
index 62adbad8e3..8796f1511a 100644
--- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts
+++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts
@@ -17,8 +17,8 @@
import {
FactRetriever,
FactRetrieverRegistration,
- FactSchema,
} from '@backstage/plugin-tech-insights-node';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
import { ConflictError, NotFoundError } from '@backstage/errors';
/**
diff --git a/plugins/tech-insights-common/src/index.ts b/plugins/tech-insights-common/src/index.ts
index 66af5c2f93..a74c1f16e9 100644
--- a/plugins/tech-insights-common/src/index.ts
+++ b/plugins/tech-insights-common/src/index.ts
@@ -134,3 +134,60 @@ export type BulkCheckResponse = Array<{
entity: string;
results: CheckResult[];
}>;
+
+/**
+ * A record type to specify individual fact shapes
+ *
+ * Used as part of a schema to validate, identify and generically construct usage implementations
+ * of individual fact values in the system.
+ *
+ * @public
+ */
+export type FactSchema = {
+ /**
+ * Name of the fact
+ */
+ [name: string]: {
+ /**
+ * Type of the individual fact value
+ *
+ * Numbers are split into integers and floating point values.
+ * `set` indicates a collection of values, `object` indicates JSON serializable value
+ */
+ type:
+ | 'integer'
+ | 'float'
+ | 'string'
+ | 'boolean'
+ | 'datetime'
+ | 'set'
+ | 'object';
+
+ /**
+ * A description of this individual fact value
+ */
+ description: string;
+
+ /**
+ * Optional semver string to indicate when this specific fact definition was added to the schema
+ */
+ since?: string;
+
+ /**
+ * Metadata related to an individual fact.
+ * Can contain links, additional description texts and other actionable data.
+ *
+ * Currently loosely typed, but in the future when patterns emerge, key shapes can be defined
+ *
+ * examples:
+ * ```
+ * \{
+ * link: 'https://sonarqube.mycompany.com/fix-these-issues',
+ * suggestion: 'To affect this value, you can do x, y, z',
+ * minValue: 0
+ * \}
+ * ```
+ */
+ metadata?: Record;
+ };
+};
diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts
index a4a0834970..3619f8b66f 100644
--- a/plugins/tech-insights-node/src/facts.ts
+++ b/plugins/tech-insights-node/src/facts.ts
@@ -20,6 +20,7 @@ import {
PluginEndpointDiscovery,
TokenManager,
} from '@backstage/backend-common';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
import { Logger } from 'winston';
/**
@@ -79,63 +80,6 @@ export type FlatTechInsightFact = TechInsightFact & {
id: string;
};
-/**
- * A record type to specify individual fact shapes
- *
- * Used as part of a schema to validate, identify and generically construct usage implementations
- * of individual fact values in the system.
- *
- * @public
- */
-export type FactSchema = {
- /**
- * Name of the fact
- */
- [name: string]: {
- /**
- * Type of the individual fact value
- *
- * Numbers are split into integers and floating point values.
- * `set` indicates a collection of values, `object` indicates JSON serializable value
- */
- type:
- | 'integer'
- | 'float'
- | 'string'
- | 'boolean'
- | 'datetime'
- | 'set'
- | 'object';
-
- /**
- * A description of this individual fact value
- */
- description: string;
-
- /**
- * Optional semver string to indicate when this specific fact definition was added to the schema
- */
- since?: string;
-
- /**
- * Metadata related to an individual fact.
- * Can contain links, additional description texts and other actionable data.
- *
- * Currently loosely typed, but in the future when patterns emerge, key shapes can be defined
- *
- * examples:
- * ```
- * \{
- * link: 'https://sonarqube.mycompany.com/fix-these-issues',
- * suggestion: 'To affect this value, you can do x, y, z',
- * minValue: 0
- * \}
- * ```
- */
- metadata?: Record;
- };
-};
-
/**
* @public
*
diff --git a/plugins/tech-insights-node/src/persistence.ts b/plugins/tech-insights-node/src/persistence.ts
index 31fda403a8..73abb6c5af 100644
--- a/plugins/tech-insights-node/src/persistence.ts
+++ b/plugins/tech-insights-node/src/persistence.ts
@@ -14,13 +14,13 @@
* limitations under the License.
*/
import {
- FactSchema,
TechInsightFact,
FlatTechInsightFact,
FactSchemaDefinition,
FactLifecycle,
} from './facts';
import { DateTime } from 'luxon';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
/**
* TechInsights Database
diff --git a/plugins/tech-insights/src/api/TechInsightsApi.ts b/plugins/tech-insights/src/api/TechInsightsApi.ts
index 166f1d2782..bb915e06dd 100644
--- a/plugins/tech-insights/src/api/TechInsightsApi.ts
+++ b/plugins/tech-insights/src/api/TechInsightsApi.ts
@@ -18,6 +18,7 @@ import { createApiRef } from '@backstage/core-plugin-api';
import {
CheckResult,
BulkCheckResponse,
+ FactSchema,
} from '@backstage/plugin-tech-insights-common';
import { Check, InsightFacts } from './types';
import { CheckResultRenderer } from '../components/CheckResultRenderer';
@@ -49,4 +50,5 @@ export interface TechInsightsApi {
checks?: Check[],
): Promise;
getFacts(entity: CompoundEntityRef, facts: string[]): Promise;
+ getFactSchemas(): Promise;
}
diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts
index 6e85769b34..a1a2e7ff77 100644
--- a/plugins/tech-insights/src/api/TechInsightsClient.ts
+++ b/plugins/tech-insights/src/api/TechInsightsClient.ts
@@ -18,6 +18,7 @@ import { TechInsightsApi } from './TechInsightsApi';
import {
BulkCheckResponse,
CheckResult,
+ FactSchema,
} from '@backstage/plugin-tech-insights-common';
import { Check, InsightFacts } from './types';
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
@@ -69,6 +70,10 @@ export class TechInsightsClient implements TechInsightsApi {
return this.api('/checks');
}
+ async getFactSchemas(): Promise {
+ return this.api('/fact-schemas');
+ }
+
async runChecks(
entityParams: CompoundEntityRef,
checks?: string[],
From f44199533a02b9e404c3079c4e0b167f96993afc Mon Sep 17 00:00:00 2001
From: "Fernando.Teixeira"
Date: Wed, 25 Jan 2023 14:28:51 -0500
Subject: [PATCH 060/156] fix(tech-inisghts): fix reference to FactSchema type
Signed-off-by: Fernando.Teixeira
---
.../src/service/persistence/TechInsightsDatabase.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts
index 7c2ba52717..c8f098a895 100644
--- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts
+++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts
@@ -16,12 +16,12 @@
import { Knex } from 'knex';
import {
FactLifecycle,
- FactSchema,
FactSchemaDefinition,
FlatTechInsightFact,
TechInsightFact,
TechInsightsStore,
} from '@backstage/plugin-tech-insights-node';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
import { rsort } from 'semver';
import { groupBy, omit } from 'lodash';
import { DateTime } from 'luxon';
From 9d59c101c8d836b1c1a22e695980e724a2c7266d Mon Sep 17 00:00:00 2001
From: "Fernando.Teixeira"
Date: Wed, 25 Jan 2023 15:03:16 -0500
Subject: [PATCH 061/156] chore(api-report): update api report for
tech-inisghts
Signed-off-by: Fernando.Teixeira
---
plugins/tech-insights-backend/api-report.md | 2 +-
plugins/tech-insights-common/api-report.md | 17 +++++++++++++++++
plugins/tech-insights-node/api-report.md | 18 +-----------------
plugins/tech-insights/api-report.md | 5 +++++
4 files changed, 24 insertions(+), 18 deletions(-)
diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md
index a757f0d67c..4b6e5b34dc 100644
--- a/plugins/tech-insights-backend/api-report.md
+++ b/plugins/tech-insights-backend/api-report.md
@@ -12,7 +12,7 @@ import { FactCheckerFactory } from '@backstage/plugin-tech-insights-node';
import { FactLifecycle } from '@backstage/plugin-tech-insights-node';
import { FactRetriever } from '@backstage/plugin-tech-insights-node';
import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node';
-import { FactSchema } from '@backstage/plugin-tech-insights-node';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
import { HumanDuration } from '@backstage/types';
import { Logger } from 'winston';
import { PluginDatabaseManager } from '@backstage/backend-common';
diff --git a/plugins/tech-insights-common/api-report.md b/plugins/tech-insights-common/api-report.md
index 0236c0619a..51ef13a013 100644
--- a/plugins/tech-insights-common/api-report.md
+++ b/plugins/tech-insights-common/api-report.md
@@ -47,5 +47,22 @@ export type FactResponse = {
};
};
+// @public
+export type FactSchema = {
+ [name: string]: {
+ type:
+ | 'integer'
+ | 'float'
+ | 'string'
+ | 'boolean'
+ | 'datetime'
+ | 'set'
+ | 'object';
+ description: string;
+ since?: string;
+ metadata?: Record;
+ };
+};
+
// (No @packageDocumentation comment for this package)
```
diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md
index 47e76b07ce..dbb8a8b674 100644
--- a/plugins/tech-insights-node/api-report.md
+++ b/plugins/tech-insights-node/api-report.md
@@ -8,6 +8,7 @@ import { Config } from '@backstage/config';
import { DateTime } from 'luxon';
import { Duration } from 'luxon';
import { DurationLike } from 'luxon';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
import { HumanDuration } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { Logger } from 'winston';
@@ -78,23 +79,6 @@ export type FactRetrieverRegistration = {
initialDelay?: Duration | HumanDuration;
};
-// @public
-export type FactSchema = {
- [name: string]: {
- type:
- | 'integer'
- | 'float'
- | 'string'
- | 'boolean'
- | 'datetime'
- | 'set'
- | 'object';
- description: string;
- since?: string;
- metadata?: Record;
- };
-};
-
// @public
export type FactSchemaDefinition = Omit;
diff --git a/plugins/tech-insights/api-report.md b/plugins/tech-insights/api-report.md
index 2d25d6d39d..d7ae337196 100644
--- a/plugins/tech-insights/api-report.md
+++ b/plugins/tech-insights/api-report.md
@@ -11,6 +11,7 @@ import { BulkCheckResponse } from '@backstage/plugin-tech-insights-common';
import { CheckResult } from '@backstage/plugin-tech-insights-common';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { DiscoveryApi } from '@backstage/core-plugin-api';
+import { FactSchema } from '@backstage/plugin-tech-insights-common';
import { IdentityApi } from '@backstage/core-plugin-api';
import { JsonValue } from '@backstage/types';
import { default as React_2 } from 'react';
@@ -74,6 +75,8 @@ export interface TechInsightsApi {
// (undocumented)
getFacts(entity: CompoundEntityRef, facts: string[]): Promise;
// (undocumented)
+ getFactSchemas(): Promise;
+ // (undocumented)
runBulkChecks(
entities: CompoundEntityRef[],
checks?: Check[],
@@ -102,6 +105,8 @@ export class TechInsightsClient implements TechInsightsApi {
// (undocumented)
getFacts(entity: CompoundEntityRef, facts: string[]): Promise;
// (undocumented)
+ getFactSchemas(): Promise;
+ // (undocumented)
runBulkChecks(
entities: CompoundEntityRef[],
checks?: Check[],
From bce8eb1bfffb6672a4c7dca395c5450dff0c8b9e Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Wed, 25 Jan 2023 17:50:04 -0600
Subject: [PATCH 062/156] Remove host encoding
Fixes bug #15874
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
plugins/azure-devops-backend/src/utils/azure-devops-utils.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts
index 0aafb54a0a..aa946e7a74 100644
--- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts
+++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts
@@ -242,12 +242,11 @@ export function buildEncodedUrl(
repo: string,
path: string,
): string {
- const encodedHost = encodeURIComponent(host);
const encodedOrg = encodeURIComponent(org);
const encodedProject = encodeURIComponent(project);
const encodedRepo = encodeURIComponent(repo);
const encodedPath = encodeURIComponent(path);
- return `https://${encodedHost}/${encodedOrg}/${encodedProject}/_git/${encodedRepo}?path=${encodedPath}`;
+ return `https://${host}/${encodedOrg}/${encodedProject}/_git/${encodedRepo}?path=${encodedPath}`;
}
function convertReviewer(
From cc926a59bd11834850ed1678cdd93b9edeb5c6a8 Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Wed, 25 Jan 2023 18:20:51 -0600
Subject: [PATCH 063/156] Add changeset
Happy cows moo!
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
.changeset/happy-cows-moo.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/happy-cows-moo.md
diff --git a/.changeset/happy-cows-moo.md b/.changeset/happy-cows-moo.md
new file mode 100644
index 0000000000..e59d9a72d0
--- /dev/null
+++ b/.changeset/happy-cows-moo.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-azure-devops-backend': patch
+---
+
+Fixed a bug in buildEncodedUrl function where port was being encoded and breaking the readme card
From 21a7b9fd83b7c70dac25de66b497e79f9299d03f Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Wed, 25 Jan 2023 18:34:14 -0600
Subject: [PATCH 064/156] Add a test for buildEncodedUrl
Protect against regression
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
.../src/utils/azure-devops-utils.test.ts | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
index 4b912a8340..ba288dad95 100644
--- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
+++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
@@ -278,3 +278,17 @@ describe('replaceReadme', () => {
expect(expected).toBe(result);
});
});
+
+describe('buildEncodedUrl', () => {
+ it('should not encode the colon between host and port', async () => {
+ const result = await buildEncodedUrl(
+ 'tfs.myorg.com:8443',
+ 'org',
+ 'project',
+ 'repo',
+ 'path'
+ );
+
+ expect(result).toBe('https://tfs.myorg.com:8443/org/project/_git/repo?path=path');
+ });
+});
From 09547ac9647390cf64e17a95557bc4c71dda58ce Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Wed, 25 Jan 2023 18:59:20 -0600
Subject: [PATCH 065/156] Ran npx prettier --write .
It looks better now.
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
.../src/utils/azure-devops-utils.test.ts | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
index ba288dad95..355919dc55 100644
--- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
+++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
@@ -281,14 +281,16 @@ describe('replaceReadme', () => {
describe('buildEncodedUrl', () => {
it('should not encode the colon between host and port', async () => {
- const result = await buildEncodedUrl(
+ const result = await buildEncodedUrl(
'tfs.myorg.com:8443',
'org',
'project',
'repo',
- 'path'
+ 'path',
+ );
+
+ expect(result).toBe(
+ 'https://tfs.myorg.com:8443/org/project/_git/repo?path=path',
);
-
- expect(result).toBe('https://tfs.myorg.com:8443/org/project/_git/repo?path=path');
});
});
From d015fe1f7ea4f92fb59a729351336438b57cd9cf Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Wed, 25 Jan 2023 19:09:18 -0600
Subject: [PATCH 066/156] Slight tweak
Was missing some words
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
.changeset/happy-cows-moo.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/happy-cows-moo.md b/.changeset/happy-cows-moo.md
index e59d9a72d0..69a8d9d4f5 100644
--- a/.changeset/happy-cows-moo.md
+++ b/.changeset/happy-cows-moo.md
@@ -2,4 +2,4 @@
'@backstage/plugin-azure-devops-backend': patch
---
-Fixed a bug in buildEncodedUrl function where port was being encoded and breaking the readme card
+Fixed a bug in buildEncodedUrl function where the colon between host name and port was being encoded and breaking the readme card
From 9e9fa77d59153ba0076b9053c7e1e222ba95b546 Mon Sep 17 00:00:00 2001
From: Patrik Oldsberg
Date: Thu, 26 Jan 2023 10:20:01 +0100
Subject: [PATCH 067/156] .github/ISSUE_TEMPLATE: add title placeholder
Signed-off-by: Patrik Oldsberg
---
.github/ISSUE_TEMPLATE/bug.yaml | 2 +-
.github/ISSUE_TEMPLATE/feature.yaml | 2 +-
.github/ISSUE_TEMPLATE/plugin.yaml | 2 +-
.github/ISSUE_TEMPLATE/rfc.yaml | 2 +-
.github/ISSUE_TEMPLATE/ux_component.yaml | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
index 782b5fdfa0..57595621e4 100644
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -1,6 +1,6 @@
name: '🐛 Bug Report'
description: 'Submit a bug report to help us improve'
-title: '🐛 Bug Report: '
+title: '🐛 Bug Report: '
labels:
- bug
body:
diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml
index b5e06d48cf..b2d5a6be81 100644
--- a/.github/ISSUE_TEMPLATE/feature.yaml
+++ b/.github/ISSUE_TEMPLATE/feature.yaml
@@ -1,6 +1,6 @@
name: 🚀 Feature
description: 'Submit a proposal for a new feature'
-title: '🚀 Feature: '
+title: '🚀 Feature: '
labels: [enhancement]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/plugin.yaml b/.github/ISSUE_TEMPLATE/plugin.yaml
index a36c427981..a7f5216631 100644
--- a/.github/ISSUE_TEMPLATE/plugin.yaml
+++ b/.github/ISSUE_TEMPLATE/plugin.yaml
@@ -1,6 +1,6 @@
name: 🔌 Plugin
description: 'Submit a proposal for a new Plugin'
-title: '🔌 Plugin: '
+title: '🔌 Plugin: '
labels: [plugin]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/rfc.yaml b/.github/ISSUE_TEMPLATE/rfc.yaml
index 775c64e674..8e73992dc8 100644
--- a/.github/ISSUE_TEMPLATE/rfc.yaml
+++ b/.github/ISSUE_TEMPLATE/rfc.yaml
@@ -1,6 +1,6 @@
name: 💬 RFC
description: 'Request For Comments (RFC) from the community'
-title: '💬 RFC: '
+title: '💬 RFC: '
labels: [rfc]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/ux_component.yaml b/.github/ISSUE_TEMPLATE/ux_component.yaml
index 453f1e8278..c528356eb3 100644
--- a/.github/ISSUE_TEMPLATE/ux_component.yaml
+++ b/.github/ISSUE_TEMPLATE/ux_component.yaml
@@ -1,6 +1,6 @@
name: 🦄 UX Component
description: 'For designers to request UX components to be added to the Backstage Storybook'
-title: '🦄 UX Component: '
+title: '🦄 UX Component: '
labels: [design]
body:
- type: markdown
From da418c89e41dbf11e95ed3d79c71105cac64c6c3 Mon Sep 17 00:00:00 2001
From: Patrik Oldsberg
Date: Thu, 26 Jan 2023 10:46:23 +0100
Subject: [PATCH 068/156] scaffolder-backend-module-sentry: fix module setup
Signed-off-by: Patrik Oldsberg
---
.changeset/hungry-news-fix.md | 5 +++++
.../package.json | 19 +++----------------
yarn.lock | 12 ------------
3 files changed, 8 insertions(+), 28 deletions(-)
create mode 100644 .changeset/hungry-news-fix.md
diff --git a/.changeset/hungry-news-fix.md b/.changeset/hungry-news-fix.md
new file mode 100644
index 0000000000..c59113d9f4
--- /dev/null
+++ b/.changeset/hungry-news-fix.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-sentry': patch
+---
+
+Fix broken module exports and dependencies to match a backend module, rather than a frontend plugin.
diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json
index 9978f0acc7..95a89371c8 100644
--- a/plugins/scaffolder-backend-module-sentry/package.json
+++ b/plugins/scaffolder-backend-module-sentry/package.json
@@ -6,11 +6,11 @@
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
- "main": "dist/index.esm.js",
+ "main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"backstage": {
- "role": "frontend-plugin"
+ "role": "backend-plugin-module"
},
"scripts": {
"start": "backstage-cli package start",
@@ -24,23 +24,10 @@
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
- "@backstage/integration": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^"
},
- "peerDependencies": {
- "react": "^16.13.1 || ^17.0.0"
- },
"devDependencies": {
- "@backstage/cli": "workspace:^",
- "@backstage/core-app-api": "workspace:^",
- "@backstage/dev-utils": "workspace:^",
- "@backstage/test-utils": "workspace:^",
- "@testing-library/jest-dom": "^5.10.1",
- "@testing-library/react": "^12.1.3",
- "@testing-library/user-event": "^14.0.0",
- "@types/node": "*",
- "cross-fetch": "^3.1.5",
- "msw": "^0.49.0"
+ "@backstage/cli": "workspace:^"
},
"files": [
"dist"
diff --git a/yarn.lock b/yarn.lock
index bcc1177fdc..ad38ddc7c7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7216,20 +7216,8 @@ __metadata:
dependencies:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
- "@backstage/core-app-api": "workspace:^"
- "@backstage/dev-utils": "workspace:^"
"@backstage/errors": "workspace:^"
- "@backstage/integration": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
- "@backstage/test-utils": "workspace:^"
- "@testing-library/jest-dom": ^5.10.1
- "@testing-library/react": ^12.1.3
- "@testing-library/user-event": ^14.0.0
- "@types/node": "*"
- cross-fetch: ^3.1.5
- msw: ^0.49.0
- peerDependencies:
- react: ^16.13.1 || ^17.0.0
languageName: unknown
linkType: soft
From 561df21ea3aa3c1596e62d06252b4dd3d3c43332 Mon Sep 17 00:00:00 2001
From: Patrik Oldsberg
Date: Thu, 26 Jan 2023 12:09:34 +0100
Subject: [PATCH 069/156] cli: set a default jest worker memory limit
Signed-off-by: Patrik Oldsberg
---
.changeset/chatty-owls-care.md | 5 +++++
packages/cli/src/commands/repo/test.ts | 7 +++++++
2 files changed, 12 insertions(+)
create mode 100644 .changeset/chatty-owls-care.md
diff --git a/.changeset/chatty-owls-care.md b/.changeset/chatty-owls-care.md
new file mode 100644
index 0000000000..84b6fc76f6
--- /dev/null
+++ b/.changeset/chatty-owls-care.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+The `backstage-cli repo test` command now sets a default Jest `--workerIdleMemoryLimit` of 1GB. This is the recommended workaround for dealing with Jest workers leaking memory and eventually hitting the heap limit.
diff --git a/packages/cli/src/commands/repo/test.ts b/packages/cli/src/commands/repo/test.ts
index 95bfe8f449..69b3d914ed 100644
--- a/packages/cli/src/commands/repo/test.ts
+++ b/packages/cli/src/commands/repo/test.ts
@@ -84,6 +84,13 @@ export async function command(opts: OptionValues, cmd: Command): Promise {
}
}
+ // When running tests from the repo root in large repos you can easily hit the heap limit.
+ // This is because Jest workers leak a lot of memory, and the workaround is to limit worker memory.
+ // We set a default memory limit, but if an explicit one is supplied it will be used instead
+ if (!args.some(arg => arg.match(/^--workerIdleMemoryLimit/))) {
+ args.push('--workerIdleMemoryLimit=1000M');
+ }
+
if (opts.since) {
removeOptionArg(args, '--since');
}
From fc73f6aae5f6dddb876a6edf5a88b7e561e7287d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?=
Date: Thu, 26 Jan 2023 12:32:52 +0100
Subject: [PATCH 070/156] switch the order of the reprocessing statements in
migrations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Fredrik Adelöw
---
.changeset/modern-cats-worry.md | 5 +++++
.../migrations/20220222164811_reprocess_for_relation_refs.js | 2 +-
.../20221109192547_search_add_original_value_column.js | 2 +-
3 files changed, 7 insertions(+), 2 deletions(-)
create mode 100644 .changeset/modern-cats-worry.md
diff --git a/.changeset/modern-cats-worry.md b/.changeset/modern-cats-worry.md
new file mode 100644
index 0000000000..b6698cb805
--- /dev/null
+++ b/.changeset/modern-cats-worry.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Switched the order of reprocessing statements retroactively in migrations. This only improves the experience for those who at a later time perform a large upgrade of an old Backstage installation.
diff --git a/plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js b/plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js
index f4be2172a0..0679d5cbdb 100644
--- a/plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js
+++ b/plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js
@@ -21,11 +21,11 @@
*/
exports.up = async function up(knex) {
// Make sure to reprocess everything, to make sure that relations have a targetRef produced
+ await knex('final_entities').update({ hash: '' });
await knex('refresh_state').update({
result_hash: '',
next_update_at: knex.fn.now(),
});
- await knex('final_entities').update({ hash: '' });
};
exports.down = async function down() {};
diff --git a/plugins/catalog-backend/migrations/20221109192547_search_add_original_value_column.js b/plugins/catalog-backend/migrations/20221109192547_search_add_original_value_column.js
index fff7955530..597006e6f5 100644
--- a/plugins/catalog-backend/migrations/20221109192547_search_add_original_value_column.js
+++ b/plugins/catalog-backend/migrations/20221109192547_search_add_original_value_column.js
@@ -34,11 +34,11 @@ exports.up = async function up(knex) {
// not enough to just reset the final_entities hash, since stitching is driven
// only by processing resulting in data that isn't matching the refresh_state
// hash.
+ await knex('final_entities').update({ hash: '' });
await knex('refresh_state').update({
result_hash: '',
next_update_at: knex.fn.now(),
});
- await knex('final_entities').update({ hash: '' });
};
/**
From 9d0900ee0b6326e8577a03c72a29537e804ca6b2 Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Thu, 26 Jan 2023 08:21:27 -0600
Subject: [PATCH 071/156] Slight tweak to changeset file
Make it clearer
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
.changeset/happy-cows-moo.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/happy-cows-moo.md b/.changeset/happy-cows-moo.md
index 69a8d9d4f5..a5e425f5d2 100644
--- a/.changeset/happy-cows-moo.md
+++ b/.changeset/happy-cows-moo.md
@@ -2,4 +2,4 @@
'@backstage/plugin-azure-devops-backend': patch
---
-Fixed a bug in buildEncodedUrl function where the colon between host name and port was being encoded and breaking the readme card
+Fixed a bug where the azure devops host in URLs on the readme card was being URL encoded, breaking hosts with ports.
From 0aeeafe7a7c2b0065f9f54cf04a4fb0ba8d91f86 Mon Sep 17 00:00:00 2001
From: matt-200 <123186429+matt-200@users.noreply.github.com>
Date: Thu, 26 Jan 2023 08:22:35 -0600
Subject: [PATCH 072/156] Add missing import
Forgot the import
Signed-off-by: matt-200 <123186429+matt-200@users.noreply.github.com>
---
.../azure-devops-backend/src/utils/azure-devops-utils.test.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
index 355919dc55..427980be14 100644
--- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
+++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts
@@ -27,6 +27,7 @@ import {
getAvatarUrl,
getPullRequestLink,
replaceReadme,
+ buildEncodedUrl,
} from './azure-devops-utils';
import { GitPullRequest } from 'azure-devops-node-api/interfaces/GitInterfaces';
import { UrlReader } from '@backstage/backend-common';
From 763dd1ccecee76180b20f3d8ba2a80b1e45fe96f Mon Sep 17 00:00:00 2001
From: Casper Thygesen
Date: Thu, 26 Jan 2023 15:28:20 +0100
Subject: [PATCH 073/156] Remove plugin-stack-overflow from packages.json in
@plugin-home
Signed-off-by: Casper Thygesen
---
plugins/home/package.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/plugins/home/package.json b/plugins/home/package.json
index ae3c03f975..39d9b68aaf 100644
--- a/plugins/home/package.json
+++ b/plugins/home/package.json
@@ -38,7 +38,6 @@
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
- "@backstage/plugin-stack-overflow": "workspace:^",
"@backstage/theme": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
From c553a625d29b06b1dc50f307a4e0c0e6dbe75ac2 Mon Sep 17 00:00:00 2001
From: Casper Thygesen
Date: Thu, 26 Jan 2023 14:39:49 +0000
Subject: [PATCH 074/156] Added changeset
Signed-off-by: Casper Thygesen