From 384c1323824976f5c18708824953aa57e0071aab Mon Sep 17 00:00:00 2001 From: shmaram Date: Wed, 22 Nov 2023 10:38:31 +0200 Subject: [PATCH 001/114] support filter in HomePageVisitedByType Signed-off-by: shmaram --- .changeset/wise-flies-laugh.md | 5 +++ .../VisitedByType/Content.test.tsx | 19 ++++++++++ .../VisitedByType/Content.tsx | 38 +++++++++++++++---- 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 .changeset/wise-flies-laugh.md diff --git a/.changeset/wise-flies-laugh.md b/.changeset/wise-flies-laugh.md new file mode 100644 index 0000000000..79a9c3bf5a --- /dev/null +++ b/.changeset/wise-flies-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': minor +--- + +Added filter support for HomePageVisitedByType in order to enable filtering entites from the list diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 4442c319e6..87b5b672a3 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -117,6 +117,25 @@ describe('', () => { expect(container.querySelectorAll('li')[0]).toBeVisible(); expect(container.querySelectorAll('li')[1]).not.toBeVisible(); }); + + it('allows items to be filtered', async () => { + const { getByText, queryByText } = await renderInTestApp( + + + + + , + ); + await waitFor(() => + expect(getByText('Explore Backstage')).toBeInTheDocument(), + ); + await waitFor(() => expect(queryByText('Tech Radar')).toBeNull()); + }); }); describe('', () => { diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 4e847b717f..400cb48e09 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -31,6 +31,11 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; + filterBy?: Array<{ + field: keyof Visit; + operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + value: string | number; + }>; }; /** @@ -43,6 +48,7 @@ export const Content = ({ numVisitsTotal, loading, kind, + filterBy, }: VisitedByTypeProps) => { const { setContext, setVisits, setLoading } = useContext(); // Allows behavior override from properties @@ -65,18 +71,34 @@ export const Content = ({ const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { return await visitsApi - .list({ - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'timestamp', direction: 'desc' }], - }) + .list( + filterBy + ? { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'timestamp', direction: 'desc' }], + filterBy, + } + : { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'timestamp', direction: 'desc' }], + }, + ) .then(setVisits); } if (!visits && !loading && kind === 'top') { return await visitsApi - .list({ - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'hits', direction: 'desc' }], - }) + .list( + filterBy + ? { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'hits', direction: 'desc' }], + filterBy, + } + : { + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'hits', direction: 'desc' }], + }, + ) .then(setVisits); } return undefined; From 8e1a0aa867a30c9d89a09ff03defc8d5e1c2d988 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 24 Nov 2023 00:45:33 +0200 Subject: [PATCH 002/114] Pull request changes Signed-off-by: shmaram --- .changeset/wise-flies-laugh.md | 2 +- plugins/home/api-report.md | 1 + .../VisitedByType/Content.test.tsx | 8 ++-- .../VisitedByType/Content.tsx | 42 ++++++------------- 4 files changed, 18 insertions(+), 35 deletions(-) diff --git a/.changeset/wise-flies-laugh.md b/.changeset/wise-flies-laugh.md index 79a9c3bf5a..d3d2ba8af0 100644 --- a/.changeset/wise-flies-laugh.md +++ b/.changeset/wise-flies-laugh.md @@ -2,4 +2,4 @@ '@backstage/plugin-home': minor --- -Added filter support for HomePageVisitedByType in order to enable filtering entites from the list +Added filter support for HomePageVisitedByType in order to enable filtering entities from the list diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 18049cae84..53baf80a0a 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -235,6 +235,7 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; + filterBy?: VisitsApiQueryParams['filterBy']; }; // @public diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 87b5b672a3..8dde5b0e1c 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -131,10 +131,10 @@ describe('', () => { , ); - await waitFor(() => - expect(getByText('Explore Backstage')).toBeInTheDocument(), - ); - await waitFor(() => expect(queryByText('Tech Radar')).toBeNull()); + await waitFor(() => { + expect(getByText('Explore Backstage')).toBeInTheDocument(); + expect(queryByText('Tech Radar')).toBeNull(); + }); }); }); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 400cb48e09..334f8c0e70 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -16,7 +16,7 @@ import React, { useEffect } from 'react'; import { VisitedByType } from './VisitedByType'; -import { Visit, visitsApiRef } from '../../api/VisitsApi'; +import { Visit, VisitsApiQueryParams, visitsApiRef } from '../../api'; import { ContextValueOnly, useContext } from './Context'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; @@ -31,11 +31,7 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; - filterBy?: Array<{ - field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; - value: string | number; - }>; + filterBy?: VisitsApiQueryParams['filterBy']; }; /** @@ -71,34 +67,20 @@ export const Content = ({ const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { return await visitsApi - .list( - filterBy - ? { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'timestamp', direction: 'desc' }], - filterBy, - } - : { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'timestamp', direction: 'desc' }], - }, - ) + .list({ + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'timestamp', direction: 'desc' }], + ...(filterBy && { filterBy }), + }) .then(setVisits); } if (!visits && !loading && kind === 'top') { return await visitsApi - .list( - filterBy - ? { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'hits', direction: 'desc' }], - filterBy, - } - : { - limit: numVisitsTotal ?? 8, - orderBy: [{ field: 'hits', direction: 'desc' }], - }, - ) + .list({ + limit: numVisitsTotal ?? 8, + orderBy: [{ field: 'hits', direction: 'desc' }], + ...(filterBy && { filterBy }), + }) .then(setVisits); } return undefined; From 6aa6f66e72c6e42e90087cc746325d4f9074da98 Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 17:50:28 +0200 Subject: [PATCH 003/114] Added support for filter using config instead of props Signed-off-by: shmaram --- .changeset/wise-flies-laugh.md | 2 +- plugins/home/README.md | 28 ++++++++ plugins/home/api-report.md | 1 - plugins/home/config.d.ts | 68 +++++++++++++++++++ plugins/home/package.json | 4 +- plugins/home/src/api/config.ts | 60 ++++++++++++++++ .../VisitedByType/Content.test.tsx | 35 +++++++--- .../VisitedByType/Content.tsx | 14 ++-- 8 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 plugins/home/config.d.ts create mode 100644 plugins/home/src/api/config.ts diff --git a/.changeset/wise-flies-laugh.md b/.changeset/wise-flies-laugh.md index d3d2ba8af0..4b31b4e9ba 100644 --- a/.changeset/wise-flies-laugh.md +++ b/.changeset/wise-flies-laugh.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-home': minor +'@backstage/plugin-home': patch --- Added filter support for HomePageVisitedByType in order to enable filtering entities from the list diff --git a/plugins/home/README.md b/plugins/home/README.md index d41ae57162..6d75bcac10 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -325,6 +325,34 @@ export default app.createRoot( ); ``` +You can filter the items that are shown in the component. +this can be done by using the config file. +Filtering is done by using 3 parameters: + +- `field` - define which field to filter. can be one of the following + - id: string; + - name: string; + - pathname: string; + - hits: number; + - timestamp: number; + - entityRef?: string; +- `operator` - can be one of the following `'<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'` +- `value` - the value of the filter + +```yaml +home: + recentVisits: + filterBy: + - field: + operator: + value: + topVisits: + filterBy: + - field: + operator: + value: +``` + ## Contributing ### Homepage Components diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 53baf80a0a..18049cae84 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -235,7 +235,6 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; - filterBy?: VisitsApiQueryParams['filterBy']; }; // @public diff --git a/plugins/home/config.d.ts b/plugins/home/config.d.ts new file mode 100644 index 0000000000..3834a14d89 --- /dev/null +++ b/plugins/home/config.d.ts @@ -0,0 +1,68 @@ +/* + * 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 interface Config { + home?: { + /** + * Top visited plugin + * @visibility frontend + */ + topVisits?: { + /** + * Filter By config + * @visibility frontend + */ + filterBy?: Array<{ + /** + * @visibility frontend + */ + field: string; + /** + * @visibility frontend + */ + operator: string; + /** + * @visibility frontend + */ + value: string | number; + }>; + }; + /** + * Recent visited plugin + * @visibility frontend + */ + recentVisits?: { + /** + * Filter By config + * @visibility frontend + */ + filterBy?: Array<{ + /** + * @visibility frontend + */ + field: string; + /** + * @visibility frontend + */ + operator: string; + /** + * @visibility frontend + */ + value: string | number; + }>; + }; + }; +} diff --git a/plugins/home/package.json b/plugins/home/package.json index 78eaf80fe3..e239332412 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -36,6 +36,7 @@ ] } }, + "configSchema": "config.d.ts", "sideEffects": false, "scripts": { "build": "backstage-cli package build", @@ -90,6 +91,7 @@ "msw": "^1.0.0" }, "files": [ - "dist" + "dist", + "config.d.ts" ] } diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts new file mode 100644 index 0000000000..893935f0e5 --- /dev/null +++ b/plugins/home/src/api/config.ts @@ -0,0 +1,60 @@ +/* + * 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 { Visit, VisitsApiQueryParams } from './VisitsApi'; +import { Config } from '@backstage/config'; + +/** + * Reads a single FilterBy config. + * + * @param config - The single config object + * + * @public + */ +export function readFilterByConfig(config: Config): { + field: keyof Visit; + operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + value: string | number; +} { + try { + const field = config.getString('field') as keyof Visit; + const operator = config.getString('operator') as + | '<' + | '<=' + | '==' + | '!=' + | '>' + | '>=' + | 'contains'; + const value = config.getString('value'); + return { field, operator, value }; + } catch (error) { + throw new Error(`Invalid config, ${error}`); + } +} + +/** + * Reads a set of filter by configs. + * + * @param configs - All of the config objects + * + * @public + */ +export function readFilterByConfigs( + configs: Config[], +): VisitsApiQueryParams['filterBy'] { + return configs.map(readFilterByConfig); +} diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 8dde5b0e1c..f45efc8781 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -16,10 +16,15 @@ import React from 'react'; import { Content } from './Content'; -import { TestApiProvider, renderInTestApp } from '@backstage/test-utils'; +import { + TestApiProvider, + renderInTestApp, + MockConfigApi, +} from '@backstage/test-utils'; import { visitsApiRef } from '../../api'; import { ContextProvider } from './Context'; import { waitFor } from '@testing-library/react'; +import { configApiRef } from '@backstage/core-plugin-api'; const visits = [ { @@ -119,15 +124,29 @@ describe('', () => { }); it('allows items to be filtered', async () => { + const configApiMock = new MockConfigApi({ + home: { + topVisits: { + filterBy: [ + { + field: 'pathname', + operator: '==', + value: '/explore', + }, + ], + }, + }, + }); + const { getByText, queryByText } = await renderInTestApp( - + - + , ); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 334f8c0e70..6fdc313c63 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -15,10 +15,11 @@ */ import React, { useEffect } from 'react'; +import { readFilterByConfigs } from '../../api/config'; import { VisitedByType } from './VisitedByType'; -import { Visit, VisitsApiQueryParams, visitsApiRef } from '../../api'; +import { Visit, visitsApiRef } from '../../api'; import { ContextValueOnly, useContext } from './Context'; -import { useApi } from '@backstage/core-plugin-api'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; /** @public */ @@ -31,7 +32,6 @@ export type VisitedByTypeProps = { numVisitsTotal?: number; loading?: boolean; kind: VisitedByTypeKind; - filterBy?: VisitsApiQueryParams['filterBy']; }; /** @@ -44,7 +44,6 @@ export const Content = ({ numVisitsTotal, loading, kind, - filterBy, }: VisitedByTypeProps) => { const { setContext, setVisits, setLoading } = useContext(); // Allows behavior override from properties @@ -62,10 +61,14 @@ export const Content = ({ setContext(state => ({ ...state, ...context })); }, [setContext, kind, visits, loading, numVisitsOpen, numVisitsTotal]); + const config = useApi(configApiRef); // Fetches data from visitsApi in case visits and loading are not provided const visitsApi = useApi(visitsApiRef); const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { + const filterBy = readFilterByConfigs( + config.getOptionalConfigArray('home.recentVisits.filterBy') ?? [], + ); return await visitsApi .list({ limit: numVisitsTotal ?? 8, @@ -75,6 +78,9 @@ export const Content = ({ .then(setVisits); } if (!visits && !loading && kind === 'top') { + const filterBy = readFilterByConfigs( + config.getOptionalConfigArray('home.topVisits.filterBy') ?? [], + ); return await visitsApi .list({ limit: numVisitsTotal ?? 8, From 9f2a817e87fd9ef4fc41538325fde0be04fa5643 Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 18:05:41 +0200 Subject: [PATCH 004/114] Fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index 6d75bcac10..37c308da60 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -335,7 +335,7 @@ Filtering is done by using 3 parameters: - pathname: string; - hits: number; - timestamp: number; - - entityRef?: string; + - entityRef: string; - `operator` - can be one of the following `'<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'` - `value` - the value of the filter From 41167e39f9754e280d6e7522490ccfadffb54b3d Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 18:11:51 +0200 Subject: [PATCH 005/114] Fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index 37c308da60..9883190be4 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -330,12 +330,12 @@ this can be done by using the config file. Filtering is done by using 3 parameters: - `field` - define which field to filter. can be one of the following - - id: string; - - name: string; - - pathname: string; - - hits: number; - - timestamp: number; - - entityRef: string; + - id: string + - name: string + - pathname: string + - hits: number + - timestamp: number + - entityRef: string - `operator` - can be one of the following `'<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'` - `value` - the value of the filter From 6640340085bff5d27deffba874385958b1be93fb Mon Sep 17 00:00:00 2001 From: shmaram Date: Tue, 5 Dec 2023 18:46:44 +0200 Subject: [PATCH 006/114] Fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index 9883190be4..c84bae684f 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -330,12 +330,12 @@ this can be done by using the config file. Filtering is done by using 3 parameters: - `field` - define which field to filter. can be one of the following - - id: string - - name: string - - pathname: string - - hits: number - - timestamp: number - - entityRef: string + - `id`: string + - `name`: string + - `pathname`: string + - `hits`: number + - `timestamp`: number + - `entityRef`: string - `operator` - can be one of the following `'<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'` - `value` - the value of the filter From 42dcdfbfd52e2cf8e118ae5f37d151365c0dd60b Mon Sep 17 00:00:00 2001 From: shmaram Date: Wed, 6 Dec 2023 21:23:01 +0200 Subject: [PATCH 007/114] Fix tests Signed-off-by: shmaram --- .../VisitedByType/Content.test.tsx | 123 +++++++++++++++++- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index f45efc8781..c6c0e6d81a 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -34,6 +34,13 @@ const visits = [ hits: 35, timestamp: Date.now() - 86400_000, }, + { + id: 'tech-radar', + name: 'Tech Radar', + pathname: '/tech-radar', + hits: 40, + timestamp: Date.now() - 360_000, + }, ]; const mockVisitsApi = { @@ -123,22 +130,24 @@ describe('', () => { expect(container.querySelectorAll('li')[1]).not.toBeVisible(); }); - it('allows items to be filtered', async () => { + it('allows recent items to be filtered using config', async () => { const configApiMock = new MockConfigApi({ home: { - topVisits: { + recentVisits: { filterBy: [ { field: 'pathname', operator: '==', - value: '/explore', + value: '/tech-radar', }, ], }, }, }); - const { getByText, queryByText } = await renderInTestApp( + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( ', () => { , ); await waitFor(() => { - expect(getByText('Explore Backstage')).toBeInTheDocument(); - expect(queryByText('Tech Radar')).toBeNull(); + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'timestamp', + }, + ], + filterBy: [{ field: 'pathname', operator: '==', value: '/tech-radar' }], + }); + }); + }); + + it('shows all recent items when there is no filtering in the config', async () => { + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'timestamp', + }, + ], + filterBy: [], + }); }); }); }); @@ -171,4 +213,73 @@ describe('', () => { expect(getByText('Explore Backstage')).toBeInTheDocument(), ); }); + + it('allows top items to be filtered using config', async () => { + const configApiMock = new MockConfigApi({ + home: { + topVisits: { + filterBy: [ + { + field: 'pathname', + operator: '==', + value: '/explore', + }, + ], + }, + }, + }); + + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'hits', + }, + ], + filterBy: [{ field: 'pathname', operator: '==', value: '/explore' }], + }); + }); + }); + + it('shows all top items when there is no filtering in the config', async () => { + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'hits', + }, + ], + filterBy: [], + }); + }); + }); }); From 5812f27ad66a5b17015960320b45ef04194e9829 Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 21 Dec 2023 11:55:10 +0200 Subject: [PATCH 008/114] Fix tests Signed-off-by: shmaram --- plugins/home/src/api/config.test.ts | 84 +++++++++++++++++++++++++++++ plugins/home/src/api/config.ts | 9 +++- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 plugins/home/src/api/config.test.ts diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts new file mode 100644 index 0000000000..62ca09dd82 --- /dev/null +++ b/plugins/home/src/api/config.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { readFilterByConfig, readFilterByConfigs } from './config'; +import { MockConfigApi } from '@backstage/test-utils'; + +describe('config', () => { + describe('readFilterByConfig', () => { + it('returns filter data', async () => { + const mockConfig = new MockConfigApi( + { + field: 'pathname', + operator: '==', + value: '3' + }); + const res = readFilterByConfig(mockConfig) + expect(res).toEqual({ + field: 'pathname', + operator: '==', + value: '3' + }) + }); + + it('throws an error for invalid filter', async () => { + const mockConfig = new MockConfigApi( + { + myField: 'pathname', + operator: '==', + value: '3' + }); + expect(() => readFilterByConfig(mockConfig)).toThrow('Invalid config, Error: Missing required config value at \'field\'') + }); + }); + + describe('readFilterByConfigs', () => { + it('returns filter data', async () => { + const mockConfig1 = new MockConfigApi( + { + field: 'id', + operator: '==', + value: '3' + }); + const mockConfig2 = new MockConfigApi( + { + field: 'pathname', + operator: '==', + value: 'path' + }); + const res = readFilterByConfigs([mockConfig1, mockConfig2]) + expect(res).toEqual([{"field": "id", "operator": "==", "value": "3"}, {"field": "pathname", "operator": "==", "value": "path"}]) + }); + + it('return undefined for invalid filter', async () => { + const mockConfig1 = new MockConfigApi( + { + field: 'id', + operator: '==', + value: '3' + }); + const mockConfig2 = new MockConfigApi( + { + myField: 'pathname', + operator: '==', + value: 'path' + }); + const res = readFilterByConfigs([mockConfig1, mockConfig2]) + expect(res).toEqual(undefined) + }); + }); +}); + diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 893935f0e5..6604084244 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -55,6 +55,11 @@ export function readFilterByConfig(config: Config): { */ export function readFilterByConfigs( configs: Config[], -): VisitsApiQueryParams['filterBy'] { - return configs.map(readFilterByConfig); +): VisitsApiQueryParams['filterBy'] | undefined { + try { + return configs.map(readFilterByConfig); + } + catch { + return undefined; + } } From 891fcd5482f57e93da64b58654a74989c219b7cf Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 22 Dec 2023 14:02:26 +0200 Subject: [PATCH 009/114] Fix tests Signed-off-by: shmaram --- plugins/home/src/api/VisitsStorageApi.test.ts | 2 +- .../VisitedByType/Content.test.tsx | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/plugins/home/src/api/VisitsStorageApi.test.ts b/plugins/home/src/api/VisitsStorageApi.test.ts index 6faeb62350..e361c624b9 100644 --- a/plugins/home/src/api/VisitsStorageApi.test.ts +++ b/plugins/home/src/api/VisitsStorageApi.test.ts @@ -250,7 +250,7 @@ describe('VisitsStorageApi.create', () => { ]); }); - it('filters by timestamp with >', async () => { + it('filters by timestamp with value >', async () => { const visits = await api.list({ filterBy: [{ field: 'timestamp', operator: '>', value: baseDate }], }); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index c6c0e6d81a..1234291f1a 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -197,6 +197,47 @@ describe('', () => { }); }); }); + + it('allows recent items to have no filter if the filter config is not valid', async () => { + const configApiMock = new MockConfigApi({ + home: { + recentVisits: { + filterBy: [ + { + operator: '==', + value: '/tech-radar', + }, + ], + }, + }, + }); + + const listSpy = jest.spyOn(mockVisitsApi, 'list'); + + await renderInTestApp( + + + + + , + ); + await waitFor(() => { + expect(listSpy).toHaveBeenCalledWith({ + limit: 8, + orderBy: [ + { + direction: 'desc', + field: 'timestamp', + }, + ], + }); + }); + }); }); describe('', () => { From 43dad25429bffbaebbeec08c22286e3485144fdc Mon Sep 17 00:00:00 2001 From: Rickard Dybeck Date: Thu, 21 Dec 2023 12:01:16 -0500 Subject: [PATCH 010/114] [CatalogBackend] Add API to get location by entity Introduce API route to get location by entity. Solves #21826. Signed-off-by: Rickard Dybeck --- .changeset/large-moons-speak.md | 6 ++ packages/catalog-client/api-report.md | 8 +++ .../catalog-client/src/CatalogClient.test.ts | 65 +++++++++++++++++++ packages/catalog-client/src/CatalogClient.ts | 12 ++++ .../src/generated/apis/DefaultApi.client.ts | 36 ++++++++++ packages/catalog-client/src/types/api.ts | 11 ++++ .../src/modules/core/DefaultLocationStore.ts | 52 ++++++++++++++- .../src/schema/openapi.generated.ts | 56 ++++++++++++++++ .../catalog-backend/src/schema/openapi.yaml | 35 ++++++++++ .../service/AuthorizedLocationService.test.ts | 33 ++++++++++ .../src/service/AuthorizedLocationService.ts | 19 +++++- .../service/DefaultLocationService.test.ts | 16 +++++ .../src/service/DefaultLocationService.ts | 5 ++ .../src/service/createRouter.test.ts | 63 ++++++++++++++++++ .../src/service/createRouter.ts | 12 ++++ plugins/catalog-backend/src/service/types.ts | 7 +- 16 files changed, 433 insertions(+), 3 deletions(-) create mode 100644 .changeset/large-moons-speak.md diff --git a/.changeset/large-moons-speak.md b/.changeset/large-moons-speak.md new file mode 100644 index 0000000000..efd94c5df3 --- /dev/null +++ b/.changeset/large-moons-speak.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-client': minor +'@backstage/plugin-catalog-backend': minor +--- + +Add API to get location by entity diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index 22feceabec..9fd9426a94 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -50,6 +50,10 @@ export interface CatalogApi { request: GetEntityFacetsRequest, options?: CatalogRequestOptions, ): Promise; + getLocationByEntity( + entityRef: CompoundEntityRef, + options?: CatalogRequestOptions, + ): Promise; getLocationById( id: string, options?: CatalogRequestOptions, @@ -120,6 +124,10 @@ export class CatalogClient implements CatalogApi { request: GetEntityFacetsRequest, options?: CatalogRequestOptions, ): Promise; + getLocationByEntity( + entityRef: CompoundEntityRef, + options?: CatalogRequestOptions, + ): Promise; getLocationById( id: string, options?: CatalogRequestOptions, diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index b8212fde96..c36b3d96b5 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -617,6 +617,71 @@ describe('CatalogClient', () => { }); }); + describe('getLocationByEntity', () => { + const defaultResponse = { + data: { + kind: 'c', + namespace: 'ns', + name: 'n', + }, + }; + + beforeEach(() => { + server.use( + rest.get(`${mockBaseUrl}/locations/by-entity/c/ns/n`, (_, res, ctx) => { + return res(ctx.json(defaultResponse)); + }), + ); + }); + + it('should locations from correct endpoint', async () => { + const response = await client.getLocationByEntity( + { kind: 'c', namespace: 'ns', name: 'n' }, + { token }, + ); + expect(response).toEqual(defaultResponse); + }); + + it('forwards authorization token', async () => { + expect.assertions(1); + + server.use( + rest.get( + `${mockBaseUrl}/locations/by-entity/c/ns/n`, + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe(`Bearer ${token}`); + return res(ctx.json(defaultResponse)); + }, + ), + ); + + await client.getLocationByEntity( + { kind: 'c', namespace: 'ns', name: 'n' }, + { token }, + ); + }); + + it('skips authorization header if token is omitted', async () => { + expect.assertions(1); + + server.use( + rest.get( + `${mockBaseUrl}/locations/by-entity/c/ns/n`, + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBeNull(); + return res(ctx.json(defaultResponse)); + }, + ), + ); + + await client.getLocationByEntity({ + kind: 'c', + namespace: 'ns', + name: 'n', + }); + }); + }); + describe('validateEntity', () => { it('returns valid false when validation fails', async () => { server.use( diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 0d6bd6b926..eb99fa7830 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -88,6 +88,18 @@ export class CatalogClient implements CatalogApi { ); } + /** + * {@inheritdoc CatalogApi.getLocationByEntity} + */ + async getLocationByEntity( + entityRef: CompoundEntityRef, + options?: CatalogRequestOptions, + ): Promise { + return await this.requestOptional( + await this.apiClient.getLocationByEntity({ path: entityRef }, options), + ); + } + /** * {@inheritdoc CatalogApi.getEntities} */ diff --git a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts index 65b8699370..8c78d4c721 100644 --- a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts +++ b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts @@ -461,6 +461,42 @@ export class DefaultApiClient { }); } + /** + * Get a location for entity. + * @param kind + * @param namespace + * @param name + */ + public async getLocationByEntity( + // @ts-ignore + request: { + path: { + kind: string; + namespace: string; + name: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations/by-entity/{kind}/{namespace}/{name}`; + + const uri = parser.parse(uriTemplate).expand({ + kind: request.path.kind, + namespace: request.path.namespace, + name: request.path.name, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + /** * Get all locations */ diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index 5285bda2f7..6d9f95b940 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -629,6 +629,17 @@ export interface CatalogApi { options?: CatalogRequestOptions, ): Promise; + /** + * Gets a location associated with an entity. + * + * @param entityRef - A reference to an entity + * @param options - Additional options + */ + getLocationByEntity( + entityRef: CompoundEntityRef, + options?: CatalogRequestOptions, + ): Promise; + /** * Validate entity and its location. * diff --git a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts index cf5db78203..267abce046 100644 --- a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts @@ -18,7 +18,11 @@ import { Location } from '@backstage/catalog-client'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import { v4 as uuid } from 'uuid'; -import { DbLocationsRow } from '../../database/tables'; +import { + DbLocationsRow, + DbRefreshStateRow, + DbSearchRow, +} from '../../database/tables'; import { getEntityLocationRef } from '../../processing/util'; import { EntityProvider, @@ -26,6 +30,11 @@ import { } from '@backstage/plugin-catalog-node'; import { locationSpecToLocationEntity } from '../../util/conversion'; import { LocationInput, LocationStore } from '../../service/types'; +import { + ANNOTATION_ORIGIN_LOCATION, + CompoundEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; export class DefaultLocationStore implements LocationStore, EntityProvider { private _connection: EntityProviderConnection | undefined; @@ -111,6 +120,47 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { }); } + async getLocationByEntity(entityRef: CompoundEntityRef): Promise { + const entityRefString = stringifyEntityRef(entityRef); + + const [entity] = await this.db('refresh_state') + .where({ entity_ref: entityRefString }) + .select('entity_id') + .limit(1); + if (!entity) { + throw new NotFoundError(`found no entity for ref ${entityRefString}`); + } + + const [locationKeyValue] = await this.db('search') + .where({ + entity_id: entity.entity_id, + key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`, + }) + .select('value') + .limit(1); + if (!locationKeyValue) { + throw new NotFoundError( + `found no origin annotation for ref ${entityRefString}`, + ); + } + const [type, ...rest] = locationKeyValue.value?.split(':') ?? []; + const target = rest.join(':'); + + // const kind, target = split[0], split[1]; + const [location] = await this.db('locations') + .where({ type, target }) + .select() + .limit(1); + + // select * from locations where type = 'split(prev)[0]' + if (!location) { + throw new NotFoundError( + `Found no location with type ${type} and target ${target}`, + ); + } + return location; + } + private get connection(): EntityProviderConnection { if (!this._connection) { throw new Error('location store is not initialized'); diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index fe64c75373..102558c9f6 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1420,6 +1420,62 @@ export const spec = { ], }, }, + '/locations/by-entity/{kind}/{namespace}/{name}': { + get: { + operationId: 'getLocationByEntity', + description: 'Get a location for entity.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Location', + }, + }, + }, + }, + default: { + $ref: '#/components/responses/ErrorResponse', + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [ + { + in: 'path', + name: 'kind', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + { + in: 'path', + name: 'namespace', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + { + in: 'path', + name: 'name', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + ], + }, + }, '/analyze-location': { post: { operationId: 'AnalyzeLocation', diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index d3ef718286..fa0f7ec371 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -1057,6 +1057,41 @@ paths: allowReserved: true schema: type: string + /locations/by-entity/{kind}/{namespace}/{name}: + get: + operationId: getLocationByEntity + description: Get a location for entity. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + default: + $ref: '#/components/responses/ErrorResponse' + security: + - {} + - JWT: [] + parameters: + - in: path + name: kind + required: true + allowReserved: true + schema: + type: string + - in: path + name: namespace + required: true + allowReserved: true + schema: + type: string + - in: path + name: name + required: true + allowReserved: true + schema: + type: string /analyze-location: post: operationId: AnalyzeLocation diff --git a/plugins/catalog-backend/src/service/AuthorizedLocationService.test.ts b/plugins/catalog-backend/src/service/AuthorizedLocationService.test.ts index e613f25cb2..c2ffee8003 100644 --- a/plugins/catalog-backend/src/service/AuthorizedLocationService.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedLocationService.test.ts @@ -24,6 +24,7 @@ describe('AuthorizedLocationService', () => { listLocations: jest.fn(), getLocation: jest.fn(), deleteLocation: jest.fn(), + getLocationByEntity: jest.fn(), }; const fakePermissionApi = { authorize: jest.fn(), @@ -144,4 +145,36 @@ describe('AuthorizedLocationService', () => { ).rejects.toThrow(NotAllowedError); }); }); + + describe('getLocationByEntity', () => { + it('calls underlying service to get location on ALLOW', async () => { + mockAllow(); + const service = createService(); + + await service.getLocationByEntity( + { kind: 'c', namespace: 'ns', name: 'n' }, + { + authorizationToken: 'Bearer authtoken', + }, + ); + + expect(fakeLocationService.getLocationByEntity).toHaveBeenCalledWith({ + kind: 'c', + namespace: 'ns', + name: 'n', + }); + }); + + it('throws error on DENY', async () => { + mockDeny(); + const service = createService(); + + await expect(() => + service.getLocationByEntity( + { kind: 'c', namespace: 'ns', name: 'n' }, + { authorizationToken: 'Bearer authtoken' }, + ), + ).rejects.toThrow(NotFoundError); + }); + }); }); diff --git a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts index 68bb1553a8..fe4547a8fc 100644 --- a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts +++ b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts @@ -15,7 +15,7 @@ */ import { Location } from '@backstage/catalog-client'; -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { NotAllowedError, NotFoundError } from '@backstage/errors'; import { catalogLocationCreatePermission, @@ -111,4 +111,21 @@ export class AuthorizedLocationService implements LocationService { return this.locationService.deleteLocation(id); } + + async getLocationByEntity( + entityRef: CompoundEntityRef, + options?: { authorizationToken?: string | undefined } | undefined, + ): Promise { + const authorizationResponse = ( + await this.permissionApi.authorize( + [{ permission: catalogLocationReadPermission }], + { token: options?.authorizationToken }, + ) + )[0]; + + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotFoundError(); + } + return this.locationService.getLocationByEntity(entityRef); + } } diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts index 6d70923556..1c17f271fb 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts @@ -28,6 +28,7 @@ describe('DefaultLocationServiceTest', () => { createLocation: jest.fn(), listLocations: jest.fn(), getLocation: jest.fn(), + getLocationByEntity: jest.fn(), }; const locationService = new DefaultLocationService(store, orchestrator); @@ -347,4 +348,19 @@ describe('DefaultLocationServiceTest', () => { expect(store.getLocation).toHaveBeenCalledWith('123'); }); }); + + describe('getLocationByEntity', () => { + it('should call locationStore.getLocationByEntity', async () => { + await locationService.getLocationByEntity({ + kind: 'c', + namespace: 'ns', + name: 'n', + }); + expect(store.getLocationByEntity).toHaveBeenCalledWith({ + kind: 'c', + namespace: 'ns', + name: 'n', + }); + }); + }); }); diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts index 6603ebc08b..5ce9456212 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts @@ -19,6 +19,7 @@ import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, stringifyEntityRef, + CompoundEntityRef, } from '@backstage/catalog-model'; import { Location } from '@backstage/catalog-client'; import { CatalogProcessingOrchestrator } from '../processing/types'; @@ -68,6 +69,10 @@ export class DefaultLocationService implements LocationService { return this.store.deleteLocation(id); } + getLocationByEntity(entityRef: CompoundEntityRef): Promise { + return this.store.getLocationByEntity(entityRef); + } + private async processEntities( unprocessedEntities: DeferredEntity[], ): Promise { diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 05ebf6e1a9..e08d3b1676 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -63,6 +63,7 @@ describe('createRouter readonly disabled', () => { createLocation: jest.fn(), listLocations: jest.fn(), deleteLocation: jest.fn(), + getLocationByEntity: jest.fn(), }; refreshService = { refresh: jest.fn() }; orchestrator = { process: jest.fn() }; @@ -621,6 +622,36 @@ describe('createRouter readonly disabled', () => { }); }); + describe('GET /locations/by-entity/:kind/:namespace/:name', () => { + it('happy path: gets location by entity ref', async () => { + const location: Location = { + id: 'foo', + type: 'url', + target: 'example.com', + }; + locationService.getLocationByEntity.mockResolvedValueOnce(location); + + const response = await request(app) + .get('/locations/by-entity/c/ns/n') + .set('authorization', 'Bearer someauthtoken'); + + expect(locationService.getLocationByEntity).toHaveBeenCalledTimes(1); + expect(locationService.getLocationByEntity).toHaveBeenCalledWith( + { kind: 'c', namespace: 'ns', name: 'n' }, + { + authorizationToken: 'someauthtoken', + }, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + id: 'foo', + target: 'example.com', + type: 'url', + }); + }); + }); + describe('POST /validate-entity', () => { describe('valid entity', () => { it('returns 200', async () => { @@ -753,6 +784,7 @@ describe('createRouter readonly enabled', () => { createLocation: jest.fn(), listLocations: jest.fn(), deleteLocation: jest.fn(), + getLocationByEntity: jest.fn(), }; const router = await createRouter({ entitiesCatalog, @@ -911,6 +943,36 @@ describe('createRouter readonly enabled', () => { expect(response.status).toEqual(403); }); }); + + describe('GET /locations/by-entity/:kind/:namespace/:name', () => { + it('happy path: gets location by entity ref', async () => { + const location: Location = { + id: 'foo', + type: 'url', + target: 'example.com', + }; + locationService.getLocationByEntity.mockResolvedValueOnce(location); + + const response = await request(app) + .get('/locations/by-entity/c/ns/n') + .set('authorization', 'Bearer someauthtoken'); + + expect(locationService.getLocationByEntity).toHaveBeenCalledTimes(1); + expect(locationService.getLocationByEntity).toHaveBeenCalledWith( + { kind: 'c', namespace: 'ns', name: 'n' }, + { + authorizationToken: 'someauthtoken', + }, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + id: 'foo', + target: 'example.com', + type: 'url', + }); + }); + }); }); describe('NextRouter permissioning', () => { @@ -944,6 +1006,7 @@ describe('NextRouter permissioning', () => { createLocation: jest.fn(), listLocations: jest.fn(), deleteLocation: jest.fn(), + getLocationByEntity: jest.fn(), }; refreshService = { refresh: jest.fn() }; const router = await createRouter({ diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 30c1fd8e3f..70b15c640d 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -290,6 +290,18 @@ export async function createRouter( ), }); res.status(204).end(); + }) + .get('/locations/by-entity/:kind/:namespace/:name', async (req, res) => { + const { kind, namespace, name } = req.params; + const output = await locationService.getLocationByEntity( + { kind, namespace, name }, + { + authorizationToken: getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ), + }, + ); + res.status(200).json(output); }); } diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index f6cfa2e783..db4dfc7f34 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { Location } from '@backstage/catalog-client'; /** @@ -48,6 +48,10 @@ export interface LocationService { id: string, options?: { authorizationToken?: string }, ): Promise; + getLocationByEntity( + entityRef: CompoundEntityRef, + options?: { authorizationToken?: string }, + ): Promise; } /** @@ -82,4 +86,5 @@ export interface LocationStore { listLocations(): Promise; getLocation(id: string): Promise; deleteLocation(id: string): Promise; + getLocationByEntity(entityRef: CompoundEntityRef): Promise; } From c33f9d485d305d38decaf7f5de45c93d9085878f Mon Sep 17 00:00:00 2001 From: Rickard Dybeck Date: Thu, 21 Dec 2023 12:35:39 -0500 Subject: [PATCH 011/114] update api docs Signed-off-by: Rickard Dybeck --- docs/features/software-catalog/api.md | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/features/software-catalog/api.md b/docs/features/software-catalog/api.md index 3ab3c0df96..46f32e9064 100644 --- a/docs/features/software-catalog/api.md +++ b/docs/features/software-catalog/api.md @@ -474,6 +474,34 @@ Response type is JSON, on the form ] ``` +### `GET /locations/{id}` + +Gets a location by it's location ID. + +Response type is JSON, on the form + +```json +{ + "id": "b9784c38-7118-472f-9e22-5638fc73bab0", + "target": "https://git.example.com/example-project/example-repository/blob/main/catalog-info.yaml", + "type": "url" +} +``` + +### `GET /locations/by-entity/{kind}/{namespace}/{name}` + +Gets a location referring to a given entity. + +Response type is JSON, on the form + +```json +{ + "id": "b9784c38-7118-472f-9e22-5638fc73bab0", + "target": "https://git.example.com/example-project/example-repository/blob/main/catalog-info.yaml", + "type": "url" +} +``` + ### `POST /locations` Adds a location to be ingested by the catalog. From 2214202d90cf88fc8b50968a662f7906a6568d42 Mon Sep 17 00:00:00 2001 From: Rickard Dybeck Date: Fri, 29 Dec 2023 10:25:53 -0500 Subject: [PATCH 012/114] review fixups Signed-off-by: Rickard Dybeck --- packages/catalog-client/src/CatalogClient.ts | 7 +++++-- packages/catalog-client/src/types/api.ts | 4 ++-- .../src/modules/core/DefaultLocationStore.ts | 4 ++-- .../src/service/AuthorizedLocationService.ts | 2 +- .../catalog-backend/src/service/DefaultLocationService.ts | 7 +++++-- plugins/catalog-backend/src/service/types.ts | 4 ++-- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index eb99fa7830..07fedd0e9c 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -92,11 +92,14 @@ export class CatalogClient implements CatalogApi { * {@inheritdoc CatalogApi.getLocationByEntity} */ async getLocationByEntity( - entityRef: CompoundEntityRef, + entityRef: CompoundEntityRef | string, options?: CatalogRequestOptions, ): Promise { return await this.requestOptional( - await this.apiClient.getLocationByEntity({ path: entityRef }, options), + await this.apiClient.getLocationByEntity( + { path: parseEntityRef(entityRef) }, + options, + ), ); } diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index 6d9f95b940..ecf2be7f44 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -632,11 +632,11 @@ export interface CatalogApi { /** * Gets a location associated with an entity. * - * @param entityRef - A reference to an entity + * @param entityRef - A complete entity ref, either on string or compound form * @param options - Additional options */ getLocationByEntity( - entityRef: CompoundEntityRef, + entityRef: string | CompoundEntityRef, options?: CatalogRequestOptions, ): Promise; diff --git a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts index 267abce046..ac506051d2 100644 --- a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts @@ -33,6 +33,7 @@ import { LocationInput, LocationStore } from '../../service/types'; import { ANNOTATION_ORIGIN_LOCATION, CompoundEntityRef, + parseLocationRef, stringifyEntityRef, } from '@backstage/catalog-model'; @@ -143,9 +144,8 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { `found no origin annotation for ref ${entityRefString}`, ); } - const [type, ...rest] = locationKeyValue.value?.split(':') ?? []; - const target = rest.join(':'); + const { type, target } = parseLocationRef(entityRefString); // const kind, target = split[0], split[1]; const [location] = await this.db('locations') .where({ type, target }) diff --git a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts index fe4547a8fc..0b9d50b20e 100644 --- a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts +++ b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts @@ -113,7 +113,7 @@ export class AuthorizedLocationService implements LocationService { } async getLocationByEntity( - entityRef: CompoundEntityRef, + entityRef: CompoundEntityRef | string, options?: { authorizationToken?: string | undefined } | undefined, ): Promise { const authorizationResponse = ( diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts index 5ce9456212..d0b603b90b 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts @@ -20,6 +20,7 @@ import { ANNOTATION_ORIGIN_LOCATION, stringifyEntityRef, CompoundEntityRef, + parseEntityRef, } from '@backstage/catalog-model'; import { Location } from '@backstage/catalog-client'; import { CatalogProcessingOrchestrator } from '../processing/types'; @@ -69,8 +70,10 @@ export class DefaultLocationService implements LocationService { return this.store.deleteLocation(id); } - getLocationByEntity(entityRef: CompoundEntityRef): Promise { - return this.store.getLocationByEntity(entityRef); + getLocationByEntity( + entityRef: CompoundEntityRef | string, + ): Promise { + return this.store.getLocationByEntity(parseEntityRef(entityRef)); } private async processEntities( diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index db4dfc7f34..878afe5662 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -49,7 +49,7 @@ export interface LocationService { options?: { authorizationToken?: string }, ): Promise; getLocationByEntity( - entityRef: CompoundEntityRef, + entityRef: CompoundEntityRef | string, options?: { authorizationToken?: string }, ): Promise; } @@ -86,5 +86,5 @@ export interface LocationStore { listLocations(): Promise; getLocation(id: string): Promise; deleteLocation(id: string): Promise; - getLocationByEntity(entityRef: CompoundEntityRef): Promise; + getLocationByEntity(entityRef: CompoundEntityRef | string): Promise; } From e0b14e39dcdbb949f3d89074dc3f5024a927864b Mon Sep 17 00:00:00 2001 From: Rickard Dybeck Date: Tue, 2 Jan 2024 11:02:39 -0500 Subject: [PATCH 013/114] fix api-report Signed-off-by: Rickard Dybeck --- packages/catalog-client/api-report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index 9fd9426a94..b1dd2b9188 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -51,7 +51,7 @@ export interface CatalogApi { options?: CatalogRequestOptions, ): Promise; getLocationByEntity( - entityRef: CompoundEntityRef, + entityRef: string | CompoundEntityRef, options?: CatalogRequestOptions, ): Promise; getLocationById( @@ -125,7 +125,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise; getLocationByEntity( - entityRef: CompoundEntityRef, + entityRef: CompoundEntityRef | string, options?: CatalogRequestOptions, ): Promise; getLocationById( From d8d243ca229a34f7bed3b3aee04d59c29df25f3c Mon Sep 17 00:00:00 2001 From: Gabriel Pereira Woitechen Date: Tue, 2 Jan 2024 15:24:45 -0300 Subject: [PATCH 014/114] fix(techdocs-cli): mkdocs parameter casing Signed-off-by: Gabriel Pereira Woitechen --- .changeset/selfish-mails-scream.md | 5 +++++ packages/techdocs-cli/src/commands/serve/serve.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/selfish-mails-scream.md diff --git a/.changeset/selfish-mails-scream.md b/.changeset/selfish-mails-scream.md new file mode 100644 index 0000000000..7d1149f27a --- /dev/null +++ b/.changeset/selfish-mails-scream.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +fix: mkdocs parameter casing diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index de681c2de4..27f432c22d 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -117,7 +117,7 @@ export default async function serve(opts: OptionValues) { stderrLogFunc: mkdocsLogFunc, mkdocsConfigFileName: mkdocsYmlPath, mkdocsParameterClean: opts.mkdocsParameterClean, - mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyReload, + mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyreload, mkdocsParameterStrict: opts.mkdocsParameterStrict, }); From e27b7f32072b96c6894f020f343d0349125803e3 Mon Sep 17 00:00:00 2001 From: secustor Date: Mon, 15 Jan 2024 15:39:13 +0100 Subject: [PATCH 015/114] fix(Github): use `x-ratelimit-remaining` and 429 status code for rate limit detection Signed-off-by: secustor --- .changeset/sweet-ravens-glow.md | 6 +++++ .../src/reading/GithubUrlReader.ts | 15 +++++++---- .../src/github/GithubIntegration.test.ts | 25 +++++++++++++++++++ .../src/github/GithubIntegration.ts | 8 ++++++ 4 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 .changeset/sweet-ravens-glow.md diff --git a/.changeset/sweet-ravens-glow.md b/.changeset/sweet-ravens-glow.md new file mode 100644 index 0000000000..045f7edbf2 --- /dev/null +++ b/.changeset/sweet-ravens-glow.md @@ -0,0 +1,6 @@ +--- +'@backstage/integration': minor +'@backstage/backend-common': patch +--- + +Fix rate limit detection by looking for HTTP status code 429 and updating the header `x-ratelimit-remaining` to look for in case of a 403 code is returned diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 764431386d..83c21b2529 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -149,10 +149,7 @@ export class GithubUrlReader implements UrlReader { // GitHub returns a 403 response with a couple of headers indicating rate // limit status. See more in the GitHub docs: // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting - if ( - response.status === 403 && - response.headers.get('X-RateLimit-Remaining') === '0' - ) { + if (this.integration.isRateLimited(response)) { message += ' (rate limit exceeded)'; } @@ -350,10 +347,18 @@ export class GithubUrlReader implements UrlReader { const response = await fetch(urlAsString, init); if (!response.ok) { - const message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; + let message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; if (response.status === 404) { throw new NotFoundError(message); } + + // GitHub returns a 403 response with a couple of headers indicating rate + // limit status. See more in the GitHub docs: + // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting + if (this.integration.isRateLimited(response)) { + message += ' (rate limit exceeded)'; + } + throw new Error(message); } diff --git a/packages/integration/src/github/GithubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts index d8e6020a64..36c802da52 100644 --- a/packages/integration/src/github/GithubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -78,6 +78,31 @@ describe('GithubIntegration', () => { ), ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); }); + + describe('isRateLimited', () => { + const integration = new GithubIntegration({ host: 'h.com' }); + + it.each` + status | ratelimitRemaining | expected + ${404} | ${100} | ${false} + ${429} | ${undefined} | ${true} + ${429} | ${100} | ${true} + ${403} | ${100} | ${false} + ${403} | ${0} | ${true} + `( + '(statusCode: $status, header: $ratelimitRemaining) === $expected', + ({ status, ratelimitRemaining, expected }) => { + const headers = new Headers({ + 'x-ratelimit-remaining': ratelimitRemaining, + }); + const result = integration.isRateLimited({ + status, + headers, + } as Response); + expect(expected).toBe(result); + }, + ); + }); }); describe('replaceGithubUrlType', () => { diff --git a/packages/integration/src/github/GithubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts index 50fc129ccf..e2b163dc2d 100644 --- a/packages/integration/src/github/GithubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -65,6 +65,14 @@ export class GithubIntegration implements ScmIntegration { resolveEditUrl(url: string): string { return replaceGithubUrlType(url, 'edit'); } + + isRateLimited(response: Response): boolean { + return ( + response.status === 429 || + (response.status === 403 && + response.headers.get('x-ratelimit-remaining') === '0') + ); + } } /** From 633250f0e5b8023c19492b4ab351b51bd3ab0313 Mon Sep 17 00:00:00 2001 From: secustor Date: Mon, 15 Jan 2024 16:03:43 +0100 Subject: [PATCH 016/114] chore: use ConsumedResponse to pass eslint validation Signed-off-by: secustor --- packages/integration/src/github/GithubIntegration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/integration/src/github/GithubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts index e2b163dc2d..7a6ac5675d 100644 --- a/packages/integration/src/github/GithubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -20,6 +20,7 @@ import { GithubIntegrationConfig, readGithubIntegrationConfigs, } from './config'; +import { ConsumedResponse } from '@backstage/errors'; /** * A GitHub based integration. @@ -66,7 +67,7 @@ export class GithubIntegration implements ScmIntegration { return replaceGithubUrlType(url, 'edit'); } - isRateLimited(response: Response): boolean { + isRateLimited(response: ConsumedResponse): boolean { return ( response.status === 429 || (response.status === 403 && From 7120dfcb871c815955ad6c709b35a98fc93e5618 Mon Sep 17 00:00:00 2001 From: secustor Date: Mon, 15 Jan 2024 16:11:30 +0100 Subject: [PATCH 017/114] fixup! chore: use ConsumedResponse to pass eslint validation Signed-off-by: secustor --- packages/integration/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/integration/package.json b/packages/integration/package.json index 7e57293903..29a4273698 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -35,6 +35,7 @@ "dependencies": { "@azure/identity": "^4.0.0", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@octokit/auth-app": "^4.0.0", "@octokit/rest": "^19.0.3", "cross-fetch": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 230f6d3e8d..7740ad7363 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4285,6 +4285,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" + "@backstage/errors": "workspace:^" "@octokit/auth-app": ^4.0.0 "@octokit/rest": ^19.0.3 "@types/luxon": ^3.0.0 From 626e790ec9c2b7f5b1b368e24d66dcaa08098a58 Mon Sep 17 00:00:00 2001 From: secustor Date: Mon, 15 Jan 2024 16:15:44 +0100 Subject: [PATCH 018/114] fixup! chore: use ConsumedResponse to pass eslint validation Signed-off-by: secustor --- packages/integration/api-report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 634e03dbfc..f830437cf1 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -4,6 +4,7 @@ ```ts import { Config } from '@backstage/config'; +import { ConsumedResponse } from '@backstage/errors'; import { RestEndpointMethodTypes } from '@octokit/rest'; // @public @@ -567,6 +568,8 @@ export class GithubIntegration implements ScmIntegration { // (undocumented) static factory: ScmIntegrationsFactory; // (undocumented) + isRateLimited(response: ConsumedResponse): boolean; + // (undocumented) resolveEditUrl(url: string): string; // (undocumented) resolveUrl(options: { From 987f565d855806a44c35f01985acfac8c4d685d1 Mon Sep 17 00:00:00 2001 From: Rutuja Marathe Date: Thu, 18 Jan 2024 22:45:57 -0500 Subject: [PATCH 019/114] feat(SearchResultListItems): Apply consistent styling Signed-off-by: Rutuja Marathe --- .changeset/eight-hounds-dream.md | 5 + .changeset/orange-walls-complain.md | 5 + .../src/search/AdrSearchResultListItem.tsx | 119 +++++++++--------- plugins/catalog/api-report.md | 2 + .../CatalogSearchResultListItem.tsx | 32 +++-- 5 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 .changeset/eight-hounds-dream.md create mode 100644 .changeset/orange-walls-complain.md diff --git a/.changeset/eight-hounds-dream.md b/.changeset/eight-hounds-dream.md new file mode 100644 index 0000000000..8bf945d684 --- /dev/null +++ b/.changeset/eight-hounds-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-adr': patch +--- + +Fix alignment of text in `AdrSearchResultListItem`. Update size and font to match other `SearchResultListItem`. diff --git a/.changeset/orange-walls-complain.md b/.changeset/orange-walls-complain.md new file mode 100644 index 0000000000..477d42d913 --- /dev/null +++ b/.changeset/orange-walls-complain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Add line clamping to `CatalogSearchResultListItem` diff --git a/plugins/adr/src/search/AdrSearchResultListItem.tsx b/plugins/adr/src/search/AdrSearchResultListItem.tsx index eebf74861d..f0c408df48 100644 --- a/plugins/adr/src/search/AdrSearchResultListItem.tsx +++ b/plugins/adr/src/search/AdrSearchResultListItem.tsx @@ -18,8 +18,6 @@ import React, { ReactNode } from 'react'; import { Box, Chip, - Divider, - ListItem, ListItemIcon, ListItemText, makeStyles, @@ -68,66 +66,63 @@ export function AdrSearchResultListItem(props: AdrSearchResultListItemProps) { if (!result) return null; return ( - <> - - {icon && {icon}} -
- - {highlight?.fields.title ? ( - - ) : ( - result.title - )} - - } - secondary={ - - {highlight?.fields.text ? ( - - ) : ( - result.text - )} - - } +
+ {icon && {icon}} +
+ + {highlight?.fields.title ? ( + + ) : ( + result.title + )} + + } + secondary={ + + {highlight?.fields.text ? ( + + ) : ( + result.text + )} + + } + /> + + - - - {result.status && ( - - )} - {result.date && ( - - )} - -
- - - + {result.status && ( + + )} + {result.date && } + +
+
); } diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index bef24b50d8..94d316d2b1 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -139,6 +139,8 @@ export interface CatalogSearchResultListItemProps { // (undocumented) icon?: ReactNode | ((result: IndexableDocument) => ReactNode); // (undocumented) + lineClamp?: number; + // (undocumented) rank?: number; // (undocumented) result?: IndexableDocument; diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index 4c6b567d8a..f2ef05b128 100644 --- a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -20,6 +20,7 @@ import { Chip, ListItemIcon, ListItemText, + Typography, makeStyles, } from '@material-ui/core'; import { Link } from '@backstage/core-components'; @@ -56,6 +57,7 @@ export interface CatalogSearchResultListItemProps { result?: IndexableDocument; highlight?: ResultHighlight; rank?: number; + lineClamp?: number; } /** @public */ @@ -94,15 +96,27 @@ export function CatalogSearchResultListItem( } secondary={ - highlight?.fields.text ? ( - - ) : ( - result.text - ) + + {highlight?.fields.text ? ( + + ) : ( + result.text + )} + } /> From d01626d0051ddddc4b5316124b6858d3339a3221 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:45:44 +0000 Subject: [PATCH 020/114] fix(deps): update typescript-eslint monorepo to v6.19.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 96 +++++++++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/yarn.lock b/yarn.lock index 33ac9d7aab..76dc8030fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19282,14 +19282,14 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^6.12.0": - version: 6.19.0 - resolution: "@typescript-eslint/eslint-plugin@npm:6.19.0" + version: 6.19.1 + resolution: "@typescript-eslint/eslint-plugin@npm:6.19.1" dependencies: "@eslint-community/regexpp": ^4.5.1 - "@typescript-eslint/scope-manager": 6.19.0 - "@typescript-eslint/type-utils": 6.19.0 - "@typescript-eslint/utils": 6.19.0 - "@typescript-eslint/visitor-keys": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.1 + "@typescript-eslint/type-utils": 6.19.1 + "@typescript-eslint/utils": 6.19.1 + "@typescript-eslint/visitor-keys": 6.19.1 debug: ^4.3.4 graphemer: ^1.4.0 ignore: ^5.2.4 @@ -19302,25 +19302,25 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 9880567d52d4e6559e2343caeed68f856d593b42816b8f705cd98d5a5b46cc620e3bebaaf08bbc982061bba18e5be94d6c539c0c816e8772ddabba0ad4e9363e + checksum: ad04000cd6c15d864ff92655baa3aec99bb0ccf4714fedd145fedde60a27590a5feafe480beb2f0f3864b416098bde1e9431bada7480eb7ca4efad891e1d2f6f languageName: node linkType: hard "@typescript-eslint/parser@npm:^6.7.2": - version: 6.19.0 - resolution: "@typescript-eslint/parser@npm:6.19.0" + version: 6.19.1 + resolution: "@typescript-eslint/parser@npm:6.19.1" dependencies: - "@typescript-eslint/scope-manager": 6.19.0 - "@typescript-eslint/types": 6.19.0 - "@typescript-eslint/typescript-estree": 6.19.0 - "@typescript-eslint/visitor-keys": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.1 + "@typescript-eslint/types": 6.19.1 + "@typescript-eslint/typescript-estree": 6.19.1 + "@typescript-eslint/visitor-keys": 6.19.1 debug: ^4.3.4 peerDependencies: eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 0ac91ff83fdf693de4494b45be79f25803ea6ca3ee717e4f96785b7ffc1da0180adb0426b61bc6eff5666c8ef9ea58c50efbd4351ef9018c0050116cbd74a62b + checksum: cd29619da08a2d9b7123ba4d8240989c747f8e0d5672179d8b147e413ee1334d1fa48570b0c37cf0ae4e26a275fd2d268cbe702c6fed639d3331abbb3292570a languageName: node linkType: hard @@ -19334,22 +19334,22 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.19.0": - version: 6.19.0 - resolution: "@typescript-eslint/scope-manager@npm:6.19.0" +"@typescript-eslint/scope-manager@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/scope-manager@npm:6.19.1" dependencies: - "@typescript-eslint/types": 6.19.0 - "@typescript-eslint/visitor-keys": 6.19.0 - checksum: 47d9d1b70cd64f9d1bb717090850e0ff1a34e453c28b43fd0cecaea4db05cacebd60f5da55b35c4b3cc01491f02e9de358f82a0822b27c00e80e3d1a27de32d1 + "@typescript-eslint/types": 6.19.1 + "@typescript-eslint/visitor-keys": 6.19.1 + checksum: 848cdebc16a3803e8a6d6035a7067605309a652bb2425f475f755b5ace4d80d2c17c8c8901f0f4759556da8d0a5b71024d472b85c3f3c70d0e6dcfe2a972ef35 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:6.19.0": - version: 6.19.0 - resolution: "@typescript-eslint/type-utils@npm:6.19.0" +"@typescript-eslint/type-utils@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/type-utils@npm:6.19.1" dependencies: - "@typescript-eslint/typescript-estree": 6.19.0 - "@typescript-eslint/utils": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.1 + "@typescript-eslint/utils": 6.19.1 debug: ^4.3.4 ts-api-utils: ^1.0.1 peerDependencies: @@ -19357,7 +19357,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: a88f022617be636f43429a7c7c5cd2e0e29955e96d4a9fed7d03467dc4a432b1240a71009d62213604ddb3522be9694e6b78882ee805687cda107021d1ddb203 + checksum: eab1a30f8d85f7c6e2545de5963fbec2f3bb91913d59623069b4b0db372a671ab048c7018376fc853c3af06ea39417f3e7b27dd665027dd812347a5e64cecd77 languageName: node linkType: hard @@ -19368,10 +19368,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:6.19.0": - version: 6.19.0 - resolution: "@typescript-eslint/types@npm:6.19.0" - checksum: 1371b5ba41c1d2879b3c2823ab01a30cf034e476ef53ff2a7f9e9a4a0056dfbbfecd3143031b05430aa6c749233cacbd01b72cea38a9ece1c6cf95a5cd43da6a +"@typescript-eslint/types@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/types@npm:6.19.1" + checksum: 598ce222b59c20432d06f60703d0c2dd16d9b2151569c192852136c57b8188e3ef6ef9fddaa2c136c9a756fcc7d873c0e29ec41cfd340564842287ef7b4571cd languageName: node linkType: hard @@ -19393,12 +19393,12 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.19.0": - version: 6.19.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.19.0" +"@typescript-eslint/typescript-estree@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/typescript-estree@npm:6.19.1" dependencies: - "@typescript-eslint/types": 6.19.0 - "@typescript-eslint/visitor-keys": 6.19.0 + "@typescript-eslint/types": 6.19.1 + "@typescript-eslint/visitor-keys": 6.19.1 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -19408,24 +19408,24 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 919f9588840cdab7e0ef6471f4c35d602523b142b2cffeabe9171d6ce65eb7f41614d0cb17e008e0d8e796374821ab053ced35b84642c3b1d491987362f2fdb5 + checksum: fb71a14aeee0468780219c5b8d39075f85d360b04ccd0ee88f4f0a615d2c232a6d3016e36d8c6eda2d9dfda86b4f4cc2c3d7582940fb29d33c7cf305e124d4e2 languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.19.0": - version: 6.19.0 - resolution: "@typescript-eslint/utils@npm:6.19.0" +"@typescript-eslint/utils@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/utils@npm:6.19.1" dependencies: "@eslint-community/eslint-utils": ^4.4.0 "@types/json-schema": ^7.0.12 "@types/semver": ^7.5.0 - "@typescript-eslint/scope-manager": 6.19.0 - "@typescript-eslint/types": 6.19.0 - "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.1 + "@typescript-eslint/types": 6.19.1 + "@typescript-eslint/typescript-estree": 6.19.1 semver: ^7.5.4 peerDependencies: eslint: ^7.0.0 || ^8.0.0 - checksum: 05a26251a526232b08850b6c3327637213ef989453e353f3a8255433b74893a70d5c38369c528b762e853b7586d7830d728b372494e65f37770ecb05a88112d4 + checksum: fe72e75c3ea17a85772b83f148555ea94ff5d55d13586f3fc038833197a74f8071e14c2bbf1781c40eec20005f052f4be2513a725eea82a15da3cb9af3046c70 languageName: node linkType: hard @@ -19457,13 +19457,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.19.0": - version: 6.19.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.19.0" +"@typescript-eslint/visitor-keys@npm:6.19.1": + version: 6.19.1 + resolution: "@typescript-eslint/visitor-keys@npm:6.19.1" dependencies: - "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/types": 6.19.1 eslint-visitor-keys: ^3.4.1 - checksum: 35b11143e1b55ecf01e0f513085df2cc83d0781f4a8354dc10f6ec3356f66b91a1ed8abadb6fb66af1c1746f9c874eabc8b5636882466e229cda5d6a39aada08 + checksum: bdf057a42e776970a89cdd568e493e3ea7ec085544d8f318d33084da63c3395ad2c0fb9cef9f61ceeca41f5dab54ab064b7078fe596889005e412ec74d2d1ae4 languageName: node linkType: hard From 01628752ed4605ed66cb1f72e548e611dfc87f19 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:03:27 +0000 Subject: [PATCH 021/114] fix(deps): update dependency @azure/identity to v4.0.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 33ac9d7aab..b01e34833c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1547,15 +1547,15 @@ __metadata: linkType: hard "@azure/identity@npm:^4.0.0": - version: 4.0.0 - resolution: "@azure/identity@npm:4.0.0" + version: 4.0.1 + resolution: "@azure/identity@npm:4.0.1" dependencies: "@azure/abort-controller": ^1.0.0 "@azure/core-auth": ^1.5.0 "@azure/core-client": ^1.4.0 "@azure/core-rest-pipeline": ^1.1.0 "@azure/core-tracing": ^1.0.0 - "@azure/core-util": ^1.0.0 + "@azure/core-util": ^1.3.0 "@azure/logger": ^1.0.0 "@azure/msal-browser": ^3.5.0 "@azure/msal-node": ^2.5.1 @@ -1564,7 +1564,7 @@ __metadata: open: ^8.0.0 stoppable: ^1.1.0 tslib: ^2.2.0 - checksum: 534a62afe4715d18e221e021f8088873b1efad34344bd4c6c4685572c89517f15a646e246e9233e8db7942b0a5c73f1961ea63e3baf39c28edde1ba51da4423d + checksum: 2c975ca70b274dc185022c2b19f1774079fd5cfb08c78ec3500e4baf973911b2958031d5fa36369d3c9acdeb25db457cc497991e7393b383103c058e097703e5 languageName: node linkType: hard From 4b0bfa237e4aab9286fe5c07aae91d6908979e10 Mon Sep 17 00:00:00 2001 From: jjangga0214 Date: Tue, 23 Jan 2024 11:56:17 +0900 Subject: [PATCH 022/114] docs(overview/threat-model): add commas Signed-off-by: jjangga0214 --- 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 cb1379aa27..115c12b307 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -52,7 +52,7 @@ As part of signing in with an identity resolver, a Backstage Token is issued con The token is used to prove the identity of the user within the Backstage system, and is used throughout Backstage plugins to control access. It is important that the ownership resolution logic is consistent across the entire Backstage ecosystem, with no possibility of misinterpreting the ownership information. -For cross backend communication the Backstage Token is typically forwarded or in strict backend to backend communication without a user party the backend itself issues a service token based on a pre-shared secret which is then validated on the receiving end. There are no unique service identities tied to these tokens at this point, meaning the tokens can be used across all services in a Backstage installation, this is something that we aim to improve in the future. +For cross-backend communication, the Backstage Token is typically forwarded or, in strict backend-to-backend communication without a user party, the backend itself issues a service token based on a pre-shared secret which is then validated on the receiving end. There are no unique service identities tied to these tokens at this point, meaning the tokens can be used across all services in a Backstage installation, this is something that we aim to improve in the future. Backstage also supports authentication through a proxy where the user identity is read from the incoming request from the proxy, which has been decorated by an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` does not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. It’s therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider. From 00c90984e5af4c0e1b0f60c641210d529e332e22 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 23 Jan 2024 16:49:16 +0100 Subject: [PATCH 023/114] add possibility to configure nunjucks configs Signed-off-by: Kiss Miklos --- .../src/lib/templating/SecureTemplater.ts | 7 +++++++ .../src/scaffolder/actions/builtin/fetch/template.ts | 6 ++++++ plugins/scaffolder-node/src/index.ts | 2 +- plugins/scaffolder-node/src/types.ts | 3 +++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 701863c7f6..8323b4aa98 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -19,6 +19,7 @@ import { resolvePackagePath } from '@backstage/backend-common'; import { TemplateFilter as _TemplateFilter, TemplateGlobal as _TemplateGlobal, + NunjucksConfigs, } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; import { JsonValue } from '@backstage/types'; @@ -34,6 +35,7 @@ const { render, renderCompat } = (() => { const env = module.exports.configure({ autoescape: false, + ...JSON.parse(nunjucksConfigs), tags: { variableStart: '\${{', variableEnd: '}}', @@ -42,6 +44,7 @@ const { render, renderCompat } = (() => { const compatEnv = module.exports.configure({ autoescape: false, + ...JSON.parse(nunjucksConfigs), tags: { variableStart: '{{', variableEnd: '}}', @@ -109,6 +112,7 @@ export interface SecureTemplaterOptions { templateFilters?: Record; /* Extra user-provided nunjucks globals */ templateGlobals?: Record; + nunjucksConfigs?: NunjucksConfigs; } export type SecureTemplateRenderer = ( @@ -122,6 +126,7 @@ export class SecureTemplater { cookiecutterCompat, templateFilters = {}, templateGlobals = {}, + nunjucksConfigs = {}, } = options; const isolate = new Isolate({ memoryLimit: 128 }); @@ -140,6 +145,8 @@ export class SecureTemplater { mkScript(nunjucksSource), ); + await contextGlobal.set('nunjucksConfigs', JSON.stringify(nunjucksConfigs)); + const availableFilters = Object.keys(templateFilters); await contextGlobal.set( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 26b249b76d..2a2c138708 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -69,6 +69,8 @@ export function createFetchTemplateAction(options: { copyWithoutTemplating?: string[]; cookiecutterCompat?: boolean; replace?: boolean; + trimBlocks?: boolean; + lstripBlocks?: boolean; }>({ id: 'fetch:template', description: @@ -237,6 +239,10 @@ export function createFetchTemplateAction(options: { ...additionalTemplateFilters, }, templateGlobals: additionalTemplateGlobals, + nunjucksConfigs: { + trimBlocks: ctx.input.trimBlocks, + lstripBlocks: ctx.input.lstripBlocks, + }, }); for (const location of allEntriesInTemplate) { diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index 0ec492dd32..a049ffd9b7 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -23,4 +23,4 @@ export * from './actions'; export * from './tasks'; export * from './files'; -export type { TemplateFilter, TemplateGlobal } from './types'; +export type { TemplateFilter, TemplateGlobal, NunjucksConfigs } from './types'; diff --git a/plugins/scaffolder-node/src/types.ts b/plugins/scaffolder-node/src/types.ts index bae106c84e..892570af80 100644 --- a/plugins/scaffolder-node/src/types.ts +++ b/plugins/scaffolder-node/src/types.ts @@ -23,3 +23,6 @@ export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; export type TemplateGlobal = | ((...args: JsonValue[]) => JsonValue | undefined) | JsonValue; + +/** @public */ +export type NunjucksConfigs = { trimBlocks?: boolean; lstripBlocks?: boolean }; From 3800d4a8ad1ee3f7109fa1a95d8e980050fa7cad Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 24 Jan 2024 11:42:10 +0100 Subject: [PATCH 024/114] add api-reports Signed-off-by: Kiss Miklos --- plugins/scaffolder-backend/api-report.md | 2 ++ plugins/scaffolder-node/api-report.md | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index c928e5341e..aa8097033e 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -175,6 +175,8 @@ export function createFetchTemplateAction(options: { copyWithoutTemplating?: string[] | undefined; cookiecutterCompat?: boolean | undefined; replace?: boolean | undefined; + trimBlocks?: boolean | undefined; + lstripBlocks?: boolean | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 6bb1534679..877f979656 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -163,6 +163,12 @@ export function initRepoAndPush(input: { commitHash: string; }>; +// @public (undocumented) +export type NunjucksConfigs = { + trimBlocks?: boolean; + lstripBlocks?: boolean; +}; + // @public (undocumented) export const parseRepoUrl: ( repoUrl: string, From e0e5afeaf614ae69c73948d76b60def09a2101b9 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 24 Jan 2024 11:44:03 +0100 Subject: [PATCH 025/114] changeset Signed-off-by: Kiss Miklos --- .changeset/young-ladybugs-decide.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/young-ladybugs-decide.md diff --git a/.changeset/young-ladybugs-decide.md b/.changeset/young-ladybugs-decide.md new file mode 100644 index 0000000000..53284cbc37 --- /dev/null +++ b/.changeset/young-ladybugs-decide.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Add option to configure nunjucks with the `trimBlocks` and `lstripBlocks` options in the fetch:template action From 89b674c1e56bccae2b55353fa93d42204ea11109 Mon Sep 17 00:00:00 2001 From: Brian Hudson Date: Wed, 24 Jan 2024 10:25:03 -0500 Subject: [PATCH 026/114] Minor perf improvement to queryEntities when limit=0 Updates queryEntities to not execute dbQuery at all when limit is 0 Signed-off-by: Brian Hudson --- .changeset/ninety-rules-sneeze.md | 5 +++++ .../catalog-backend/src/service/DefaultEntitiesCatalog.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/ninety-rules-sneeze.md diff --git a/.changeset/ninety-rules-sneeze.md b/.changeset/ninety-rules-sneeze.md new file mode 100644 index 0000000000..9efc790861 --- /dev/null +++ b/.changeset/ninety-rules-sneeze.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Minor performance improvement for queryEntities when the limit is 0. diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 0245fe1d4e..81982d5617 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -451,7 +451,7 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { countQuery.count('search.entity_id', { as: 'count' }); const [rows, [{ count }]] = await Promise.all([ - dbQuery, + limit > 0 ? dbQuery : [], // for performance reasons we invoke the countQuery // only on the first request. // The result is then embedded into the cursor From ecc4f0eb8835b0564c32e9e4bb4390789175e24c Mon Sep 17 00:00:00 2001 From: Brian Hudson Date: Wed, 24 Jan 2024 10:34:00 -0500 Subject: [PATCH 027/114] Fix changeset Signed-off-by: Brian Hudson --- .changeset/ninety-rules-sneeze.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ninety-rules-sneeze.md b/.changeset/ninety-rules-sneeze.md index 9efc790861..bea9d00f7e 100644 --- a/.changeset/ninety-rules-sneeze.md +++ b/.changeset/ninety-rules-sneeze.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Minor performance improvement for queryEntities when the limit is 0. +Minor performance improvement for `queryEntities` when the limit is 0. From 1ba7f03f4932fa80923a0eecdf9e70de16b9bddc Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 24 Jan 2024 14:14:06 -0600 Subject: [PATCH 028/114] add forceFork input to createPullRequest Signed-off-by: Abel Rodriguez --- .../src/actions/githubPullRequest.test.ts | 53 +++++++++++++++++++ .../src/actions/githubPullRequest.ts | 8 +++ 2 files changed, 61 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index c5b1038a01..3fe4f62e04 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -687,4 +687,57 @@ describe('createPublishGithubPullRequestAction', () => { }); }); }); + + describe('with force fork', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + forceFork: true, + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + }); + + it('creates a pull request', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + }, + ], + forceFork: true, + }); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 2d8dc7ce8d..6a5c939905 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -135,6 +135,7 @@ export const createPublishGithubPullRequestAction = ( teamReviewers?: string[]; commitMessage?: string; update?: boolean; + forceFork?: boolean; }>({ id: 'publish:github:pull-request', examples, @@ -217,6 +218,11 @@ export const createPublishGithubPullRequestAction = ( title: 'Update', description: 'Update pull request if already exists', }, + forceFork: { + type: 'boolean', + title: 'Force Fork', + description: 'Create pull request from a fork', + }, }, }, output: { @@ -255,6 +261,7 @@ export const createPublishGithubPullRequestAction = ( teamReviewers, commitMessage, update, + forceFork, } = ctx.input; const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -327,6 +334,7 @@ export const createPublishGithubPullRequestAction = ( head: branchName, draft, update, + forceFork, }; if (targetBranchName) { createOptions.base = targetBranchName; From fd5eb1c176dd855882dc997231d41e1ab2d753d3 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 24 Jan 2024 14:32:23 -0600 Subject: [PATCH 029/114] add changeset Signed-off-by: Abel Rodriguez --- .changeset/calm-items-double.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/calm-items-double.md diff --git a/.changeset/calm-items-double.md b/.changeset/calm-items-double.md new file mode 100644 index 0000000000..c16f989845 --- /dev/null +++ b/.changeset/calm-items-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Allow to force the creation of a pull request from a forked repository From 838b74fe6a8c7c9a0bbdc6dbac1fb9beeabc84b4 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 24 Jan 2024 14:41:43 -0600 Subject: [PATCH 030/114] update api reports Signed-off-by: Abel Rodriguez --- plugins/scaffolder-backend-module-github/api-report.md | 1 + plugins/scaffolder-backend/api-report.md | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 920be79d12..42aee02014 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -374,6 +374,7 @@ export const createPublishGithubPullRequestAction: ( teamReviewers?: string[] | undefined; commitMessage?: string | undefined; update?: boolean | undefined; + forceFork?: boolean | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index c928e5341e..b231267ede 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -266,6 +266,7 @@ export const createPublishGithubPullRequestAction: ( teamReviewers?: string[] | undefined; commitMessage?: string | undefined; update?: boolean | undefined; + forceFork?: boolean | undefined; }, JsonObject >; From 6f704931fb0aa4331c06d807a80430eb6a62a3d0 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Wed, 24 Jan 2024 14:49:09 -0600 Subject: [PATCH 031/114] update changeset Signed-off-by: Abel Rodriguez --- .changeset/calm-items-double.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/calm-items-double.md b/.changeset/calm-items-double.md index c16f989845..8245fb06c4 100644 --- a/.changeset/calm-items-double.md +++ b/.changeset/calm-items-double.md @@ -1,4 +1,5 @@ --- +'@backstage/plugin-scaffolder-backend-module-github': minor '@backstage/plugin-scaffolder-backend': minor --- From 27cb093b3c71fe276e4352d0a5c5bfb78a5c1dc3 Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 25 Jan 2024 08:22:21 +0200 Subject: [PATCH 032/114] Rename config functions + update README.md Signed-off-by: shmaram --- plugins/home/README.md | 4 + plugins/home/src/api/config.test.ts | 100 +++++++++--------- plugins/home/src/api/config.ts | 9 +- .../VisitedByType/Content.tsx | 6 +- 4 files changed, 63 insertions(+), 56 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index c84bae684f..fdc5ce833b 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -353,6 +353,10 @@ home: value: ``` +Filters that are not defined according to the docs will be ignored. + +In order to validate the config you can use `backstage/cli config:check` + ## Contributing ### Homepage Components diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts index 62ca09dd82..c2b715ec08 100644 --- a/plugins/home/src/api/config.test.ts +++ b/plugins/home/src/api/config.test.ts @@ -14,71 +14,75 @@ * limitations under the License. */ -import { readFilterByConfig, readFilterByConfigs } from './config'; +import { readFilterConfig, createFilterByQueryParamFromConfig } from './config'; import { MockConfigApi } from '@backstage/test-utils'; describe('config', () => { - describe('readFilterByConfig', () => { + describe('readFilterConfig', () => { it('returns filter data', async () => { - const mockConfig = new MockConfigApi( - { - field: 'pathname', - operator: '==', - value: '3' - }); - const res = readFilterByConfig(mockConfig) + const mockConfig = new MockConfigApi({ + field: 'pathname', + operator: '==', + value: '3', + }); + const res = readFilterConfig(mockConfig); expect(res).toEqual({ field: 'pathname', operator: '==', - value: '3' - }) + value: '3', + }); }); it('throws an error for invalid filter', async () => { - const mockConfig = new MockConfigApi( - { - myField: 'pathname', - operator: '==', - value: '3' - }); - expect(() => readFilterByConfig(mockConfig)).toThrow('Invalid config, Error: Missing required config value at \'field\'') + const mockConfig = new MockConfigApi({ + myField: 'pathname', + operator: '==', + value: '3', + }); + expect(() => readFilterConfig(mockConfig)).toThrow( + "Invalid config, Error: Missing required config value at 'field'", + ); }); }); - describe('readFilterByConfigs', () => { + describe('createFilterByQueryParamFromConfig', () => { it('returns filter data', async () => { - const mockConfig1 = new MockConfigApi( - { - field: 'id', - operator: '==', - value: '3' - }); - const mockConfig2 = new MockConfigApi( - { - field: 'pathname', - operator: '==', - value: 'path' - }); - const res = readFilterByConfigs([mockConfig1, mockConfig2]) - expect(res).toEqual([{"field": "id", "operator": "==", "value": "3"}, {"field": "pathname", "operator": "==", "value": "path"}]) + const mockConfig1 = new MockConfigApi({ + field: 'id', + operator: '==', + value: '3', + }); + const mockConfig2 = new MockConfigApi({ + field: 'pathname', + operator: '==', + value: 'path', + }); + const res = createFilterByQueryParamFromConfig([ + mockConfig1, + mockConfig2, + ]); + expect(res).toEqual([ + { field: 'id', operator: '==', value: '3' }, + { field: 'pathname', operator: '==', value: 'path' }, + ]); }); it('return undefined for invalid filter', async () => { - const mockConfig1 = new MockConfigApi( - { - field: 'id', - operator: '==', - value: '3' - }); - const mockConfig2 = new MockConfigApi( - { - myField: 'pathname', - operator: '==', - value: 'path' - }); - const res = readFilterByConfigs([mockConfig1, mockConfig2]) - expect(res).toEqual(undefined) + const mockConfig1 = new MockConfigApi({ + field: 'id', + operator: '==', + value: '3', + }); + const mockConfig2 = new MockConfigApi({ + myField: 'pathname', + operator: '==', + value: 'path', + }); + const res = createFilterByQueryParamFromConfig([ + mockConfig1, + mockConfig2, + ]); + expect(res).toEqual(undefined); }); }); }); - diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 6604084244..a8af29fc9e 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -24,7 +24,7 @@ import { Config } from '@backstage/config'; * * @public */ -export function readFilterByConfig(config: Config): { +export function readFilterConfig(config: Config): { field: keyof Visit; operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; value: string | number; @@ -53,13 +53,12 @@ export function readFilterByConfig(config: Config): { * * @public */ -export function readFilterByConfigs( +export function createFilterByQueryParamFromConfig( configs: Config[], ): VisitsApiQueryParams['filterBy'] | undefined { try { - return configs.map(readFilterByConfig); - } - catch { + return configs.map(readFilterConfig); + } catch { return undefined; } } diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx index 6fdc313c63..6c9d889d72 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect } from 'react'; -import { readFilterByConfigs } from '../../api/config'; +import { createFilterByQueryParamFromConfig } from '../../api/config'; import { VisitedByType } from './VisitedByType'; import { Visit, visitsApiRef } from '../../api'; import { ContextValueOnly, useContext } from './Context'; @@ -66,7 +66,7 @@ export const Content = ({ const visitsApi = useApi(visitsApiRef); const { loading: reqLoading } = useAsync(async () => { if (!visits && !loading && kind === 'recent') { - const filterBy = readFilterByConfigs( + const filterBy = createFilterByQueryParamFromConfig( config.getOptionalConfigArray('home.recentVisits.filterBy') ?? [], ); return await visitsApi @@ -78,7 +78,7 @@ export const Content = ({ .then(setVisits); } if (!visits && !loading && kind === 'top') { - const filterBy = readFilterByConfigs( + const filterBy = createFilterByQueryParamFromConfig( config.getOptionalConfigArray('home.topVisits.filterBy') ?? [], ); return await visitsApi From 7944df5ae449854603bffd83f35e65b993bcd68e Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 25 Jan 2024 10:04:48 +0200 Subject: [PATCH 033/114] update package.json Signed-off-by: shmaram --- plugins/home/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/home/package.json b/plugins/home/package.json index 603b286c7a..e8fa2b666c 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -50,6 +50,7 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 0e3d81bc7f..458717f31a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7021,6 +7021,7 @@ __metadata: "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" From 7052918391cc84d98d0c553682da96ffb2d88992 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 25 Jan 2024 16:04:35 +0100 Subject: [PATCH 034/114] do not export the SecureTemplateOptions Signed-off-by: Kiss Miklos --- .../src/lib/templating/SecureTemplater.ts | 5 ++--- plugins/scaffolder-node/api-report.md | 6 ------ plugins/scaffolder-node/src/index.ts | 2 +- plugins/scaffolder-node/src/types.ts | 3 --- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 8323b4aa98..607b1c6add 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -19,7 +19,6 @@ import { resolvePackagePath } from '@backstage/backend-common'; import { TemplateFilter as _TemplateFilter, TemplateGlobal as _TemplateGlobal, - NunjucksConfigs, } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; import { JsonValue } from '@backstage/types'; @@ -105,14 +104,14 @@ export type TemplateFilter = _TemplateFilter; */ export type TemplateGlobal = _TemplateGlobal; -export interface SecureTemplaterOptions { +interface SecureTemplaterOptions { /* Enables jinja compatibility and the "jsonify" filter */ cookiecutterCompat?: boolean; /* Extra user-provided nunjucks filters */ templateFilters?: Record; /* Extra user-provided nunjucks globals */ templateGlobals?: Record; - nunjucksConfigs?: NunjucksConfigs; + nunjucksConfigs?: { trimBlocks?: boolean; lstripBlocks?: boolean }; } export type SecureTemplateRenderer = ( diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 877f979656..6bb1534679 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -163,12 +163,6 @@ export function initRepoAndPush(input: { commitHash: string; }>; -// @public (undocumented) -export type NunjucksConfigs = { - trimBlocks?: boolean; - lstripBlocks?: boolean; -}; - // @public (undocumented) export const parseRepoUrl: ( repoUrl: string, diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index a049ffd9b7..0ec492dd32 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -23,4 +23,4 @@ export * from './actions'; export * from './tasks'; export * from './files'; -export type { TemplateFilter, TemplateGlobal, NunjucksConfigs } from './types'; +export type { TemplateFilter, TemplateGlobal } from './types'; diff --git a/plugins/scaffolder-node/src/types.ts b/plugins/scaffolder-node/src/types.ts index 892570af80..bae106c84e 100644 --- a/plugins/scaffolder-node/src/types.ts +++ b/plugins/scaffolder-node/src/types.ts @@ -23,6 +23,3 @@ export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; export type TemplateGlobal = | ((...args: JsonValue[]) => JsonValue | undefined) | JsonValue; - -/** @public */ -export type NunjucksConfigs = { trimBlocks?: boolean; lstripBlocks?: boolean }; From 8723c5a4da06ca7c11f84f3bbc9ae91527fd6499 Mon Sep 17 00:00:00 2001 From: David Festal Date: Thu, 25 Jan 2024 16:21:45 +0100 Subject: [PATCH 035/114] Fix wrong `alpha` support in dynamic plugins support The `alpha` sub-package should not be required for the dynamic plugins to be loaded under the new backend system. Signed-off-by: David Festal --- .changeset/calm-cups-rule.md | 5 +++ .../src/scanner/plugin-scanner.test.ts | 31 ++++++++++++++++++ .../src/scanner/plugin-scanner.ts | 32 ++++++++++--------- 3 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 .changeset/calm-cups-rule.md diff --git a/.changeset/calm-cups-rule.md b/.changeset/calm-cups-rule.md new file mode 100644 index 0000000000..48cb8a2012 --- /dev/null +++ b/.changeset/calm-cups-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Fix wrong `alpha` support in dynamic plugins support: the `alpha` sub-package should not be required for the dynamic plugins to be loaded under the new backend system. diff --git a/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts index e52eee87c4..1abd7c234c 100644 --- a/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts +++ b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts @@ -484,6 +484,37 @@ Please add '${mockDir.resolve( ], }, }, + { + name: "alpha manifest preferred but skipped because the `alpha` sub-directory doesn't exist", + preferAlpha: true, + fileSystem: { + backstageRoot: { + 'dist-dynamic': { + 'test-backend-plugin': { + 'package.json': JSON.stringify({ + name: 'test-backend-plugin-dynamic', + version: '0.0.0', + main: 'dist/index.cjs.js', + backstage: { role: 'backend-plugin' }, + }), + }, + }, + }, + }, + expectedPluginPackages: [ + { + location: url.pathToFileURL( + mockDir.resolve('backstageRoot/dist-dynamic/test-backend-plugin'), + ), + manifest: { + name: 'test-backend-plugin-dynamic', + version: '0.0.0', + main: 'dist/index.cjs.js', + backstage: { role: 'backend-plugin' }, + }, + }, + ], + }, { name: 'invalid alpha package.json', preferAlpha: true, diff --git a/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts index 25e0d614dd..af1144f019 100644 --- a/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts +++ b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts @@ -16,7 +16,7 @@ import { Config } from '@backstage/config'; import { ScannedPluginPackage, ScannedPluginManifest } from './types'; import * as fs from 'fs/promises'; -import { Stats, lstatSync } from 'fs'; +import { Stats, lstatSync, existsSync } from 'fs'; import * as chokidar from 'chokidar'; import * as path from 'path'; import * as url from 'url'; @@ -178,22 +178,24 @@ export class PluginScanner { if (platform === 'node') { if (this.preferAlpha) { const pluginHomeAlpha = path.resolve(pluginHome, 'alpha'); - if ((await fs.lstat(pluginHomeAlpha)).isDirectory()) { - const backstage = scannedPlugin.manifest.backstage; - try { - scannedPlugin = await this.scanDir(pluginHomeAlpha); - } catch (e) { - this.logger.error( - `failed to load dynamic plugin manifest from '${pluginHomeAlpha}'`, - e, + if (existsSync(pluginHomeAlpha)) { + if ((await fs.lstat(pluginHomeAlpha)).isDirectory()) { + const backstage = scannedPlugin.manifest.backstage; + try { + scannedPlugin = await this.scanDir(pluginHomeAlpha); + } catch (e) { + this.logger.error( + `failed to load dynamic plugin manifest from '${pluginHomeAlpha}'`, + e, + ); + continue; + } + scannedPlugin.manifest.backstage = backstage; + } else { + this.logger.warn( + `skipping '${pluginHomeAlpha}' since it is not a directory`, ); - continue; } - scannedPlugin.manifest.backstage = backstage; - } else { - this.logger.warn( - `skipping '${pluginHomeAlpha}' since it is not a directory`, - ); } } } From 75b51a87b6b392616a28cde39fb30de0918b5e1f Mon Sep 17 00:00:00 2001 From: secustor Date: Thu, 25 Jan 2024 19:32:54 +0100 Subject: [PATCH 036/114] return interface instead of boolean Signed-off-by: secustor --- .../src/reading/GithubUrlReader.ts | 2 +- packages/integration/api-report.md | 8 +++++++- .../src/github/GithubIntegration.test.ts | 6 ++++-- .../src/github/GithubIntegration.ts | 19 ++++++++++++------- packages/integration/src/index.ts | 1 + packages/integration/src/types.ts | 9 +++++++++ 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index edd5269649..462821a20f 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -333,7 +333,7 @@ export class GithubUrlReader implements UrlReader { // GitHub returns a 403 response with a couple of headers indicating rate // limit status. See more in the GitHub docs: // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting - if (this.integration.isRateLimited(response)) { + if (this.integration.parseRateLimitInfo(response).isRateLimited) { message += ' (rate limit exceeded)'; } diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index f830437cf1..07b4f7d012 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -568,7 +568,7 @@ export class GithubIntegration implements ScmIntegration { // (undocumented) static factory: ScmIntegrationsFactory; // (undocumented) - isRateLimited(response: ConsumedResponse): boolean; + parseRateLimitInfo(response: ConsumedResponse): RateLimitInfo; // (undocumented) resolveEditUrl(url: string): string; // (undocumented) @@ -697,6 +697,12 @@ export type PersonalAccessTokenCredential = AzureCredentialBase & { personalAccessToken: string; }; +// @public +export interface RateLimitInfo { + // (undocumented) + isRateLimited: boolean; +} + // @public export function readAwsS3IntegrationConfig( config: Config, diff --git a/packages/integration/src/github/GithubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts index 36c802da52..70f1c4a16c 100644 --- a/packages/integration/src/github/GithubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -95,11 +95,13 @@ describe('GithubIntegration', () => { const headers = new Headers({ 'x-ratelimit-remaining': ratelimitRemaining, }); - const result = integration.isRateLimited({ + const result = integration.parseRateLimitInfo({ status, headers, } as Response); - expect(expected).toBe(result); + expect(result).toMatchObject({ + isRateLimited: expected, + }); }, ); }); diff --git a/packages/integration/src/github/GithubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts index 7a6ac5675d..880a2bed21 100644 --- a/packages/integration/src/github/GithubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -15,7 +15,11 @@ */ import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; -import { ScmIntegration, ScmIntegrationsFactory } from '../types'; +import { + RateLimitInfo, + ScmIntegration, + ScmIntegrationsFactory, +} from '../types'; import { GithubIntegrationConfig, readGithubIntegrationConfigs, @@ -67,12 +71,13 @@ export class GithubIntegration implements ScmIntegration { return replaceGithubUrlType(url, 'edit'); } - isRateLimited(response: ConsumedResponse): boolean { - return ( - response.status === 429 || - (response.status === 403 && - response.headers.get('x-ratelimit-remaining') === '0') - ); + parseRateLimitInfo(response: ConsumedResponse): RateLimitInfo { + return { + isRateLimited: + response.status === 429 || + (response.status === 403 && + response.headers.get('x-ratelimit-remaining') === '0'), + }; } } diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index 5738da3da0..700d7dc67c 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -37,5 +37,6 @@ export type { ScmIntegration, ScmIntegrationsFactory, ScmIntegrationsGroup, + RateLimitInfo, } from './types'; export type { ScmIntegrationRegistry } from './registry'; diff --git a/packages/integration/src/types.ts b/packages/integration/src/types.ts index cb4b78eb7c..0f0fca942e 100644 --- a/packages/integration/src/types.ts +++ b/packages/integration/src/types.ts @@ -108,3 +108,12 @@ export interface ScmIntegrationsGroup { export type ScmIntegrationsFactory = (options: { config: Config; }) => ScmIntegrationsGroup; + +/** + * Encapsulates information about the RateLimit state + * + * @public + */ +export interface RateLimitInfo { + isRateLimited: boolean; +} From c57d61f0a9c2754bcb019fc8d555a14c5c88ab99 Mon Sep 17 00:00:00 2001 From: shmaram Date: Thu, 25 Jan 2024 23:10:30 +0200 Subject: [PATCH 037/114] Return undefined for invalid filters + fix README.md Signed-off-by: shmaram --- plugins/home/README.md | 2 +- plugins/home/src/api/config.test.ts | 21 ++++++------ plugins/home/src/api/config.ts | 19 +++++++---- .../VisitedByType/Content.test.tsx | 32 ++++++++++++++++++- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/plugins/home/README.md b/plugins/home/README.md index fdc5ce833b..066e637725 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -353,7 +353,7 @@ home: value: ``` -Filters that are not defined according to the docs will be ignored. +`filterBy` configs that are not defined in the above format will be ignored. In order to validate the config you can use `backstage/cli config:check` diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts index c2b715ec08..178287bb3d 100644 --- a/plugins/home/src/api/config.test.ts +++ b/plugins/home/src/api/config.test.ts @@ -33,15 +33,14 @@ describe('config', () => { }); }); - it('throws an error for invalid filter', async () => { - const mockConfig = new MockConfigApi({ + it('returns undefined for invalid filter', async () => { + const mockInvalidConfig = new MockConfigApi({ myField: 'pathname', operator: '==', value: '3', }); - expect(() => readFilterConfig(mockConfig)).toThrow( - "Invalid config, Error: Missing required config value at 'field'", - ); + const res = readFilterConfig(mockInvalidConfig); + expect(res).toEqual(undefined); }); }); @@ -67,22 +66,22 @@ describe('config', () => { ]); }); - it('return undefined for invalid filter', async () => { - const mockConfig1 = new MockConfigApi({ + it('returns only invalid filters', async () => { + const mockValidConfig = new MockConfigApi({ field: 'id', operator: '==', value: '3', }); - const mockConfig2 = new MockConfigApi({ + const mockInvalidConfig = new MockConfigApi({ myField: 'pathname', operator: '==', value: 'path', }); const res = createFilterByQueryParamFromConfig([ - mockConfig1, - mockConfig2, + mockValidConfig, + mockInvalidConfig, ]); - expect(res).toEqual(undefined); + expect(res).toEqual([{ field: 'id', operator: '==', value: '3' }]); }); }); }); diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index a8af29fc9e..3cc6d93f24 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -24,11 +24,13 @@ import { Config } from '@backstage/config'; * * @public */ -export function readFilterConfig(config: Config): { - field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; - value: string | number; -} { +export function readFilterConfig(config: Config): + | { + field: keyof Visit; + operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + value: string | number; + } + | undefined { try { const field = config.getString('field') as keyof Visit; const operator = config.getString('operator') as @@ -42,7 +44,8 @@ export function readFilterConfig(config: Config): { const value = config.getString('value'); return { field, operator, value }; } catch (error) { - throw new Error(`Invalid config, ${error}`); + // invalid filter config - ignore filter + return undefined; } } @@ -57,7 +60,9 @@ export function createFilterByQueryParamFromConfig( configs: Config[], ): VisitsApiQueryParams['filterBy'] | undefined { try { - return configs.map(readFilterConfig); + return configs + .map(readFilterConfig) + .filter(Boolean) as VisitsApiQueryParams['filterBy']; } catch { return undefined; } diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index 1234291f1a..2a7a6481ba 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -43,12 +43,21 @@ const visits = [ }, ]; -const mockVisitsApi = { +let mockVisitsApi = { save: async () => visits[0], list: async () => visits, }; describe('', () => { + beforeEach(() => { + mockVisitsApi = { + save: async () => visits[0], + list: async () => visits, + }; + }); + + afterEach(() => jest.resetAllMocks()); + it('renders', async () => { const { getByText } = await renderInTestApp( @@ -207,6 +216,11 @@ describe('', () => { operator: '==', value: '/tech-radar', }, + { + field: 'pathname', + operator: '==', + value: '/explore', + }, ], }, }, @@ -235,12 +249,28 @@ describe('', () => { field: 'timestamp', }, ], + filterBy: [ + { + field: 'pathname', + operator: '==', + value: '/explore', + }, + ], }); }); }); }); describe('', () => { + beforeEach(() => { + mockVisitsApi = { + save: async () => visits[0], + list: async () => visits, + }; + }); + + afterEach(() => jest.resetAllMocks()); + it('renders', async () => { const { getByText } = await renderInTestApp( From 94771339d8cb346e58618a134303438ab67e402e Mon Sep 17 00:00:00 2001 From: Jonathan Nagayoshi Date: Thu, 25 Jan 2024 21:24:42 +0000 Subject: [PATCH 038/114] fix(catalog-backend-module-github): decreases number of teams fetched in graphql at a time Signed-off-by: Jonathan Nagayoshi --- .changeset/nice-carrots-dream.md | 5 +++++ plugins/catalog-backend-module-github/src/lib/github.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/nice-carrots-dream.md diff --git a/.changeset/nice-carrots-dream.md b/.changeset/nice-carrots-dream.md new file mode 100644 index 0000000000..3b57dfd9e0 --- /dev/null +++ b/.changeset/nice-carrots-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Decreased number of teams fetched by GraphQL Query responsible for fetching Teams and Members in organization, due to timeouts when running against big organizations diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index cf6b400443..eff52ea98d 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -193,7 +193,7 @@ export async function getOrganizationTeams( const query = ` query teams($org: String!, $cursor: String) { organization(login: $org) { - teams(first: 100, after: $cursor) { + teams(first: 50, after: $cursor) { pageInfo { hasNextPage, endCursor } nodes { slug From 11e0767761a97b97663b7660d713624cfa18506e Mon Sep 17 00:00:00 2001 From: Emmanuel Auffray Date: Fri, 26 Jan 2024 10:42:22 +1300 Subject: [PATCH 039/114] Update template.examples.ts, typo fix on skeleton small typo fix Signed-off-by: Emmanuel Auffray --- .../src/scaffolder/actions/builtin/fetch/template.examples.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.ts index 4c4628e715..1e2212b4ee 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.ts @@ -20,7 +20,7 @@ import yaml from 'yaml'; export const examples: TemplateExample[] = [ { description: - 'Downloads a skelaton directory that lives alongside the template file and fill it out with values.', + 'Downloads a skeleton directory that lives alongside the template file and fill it out with values.', example: yaml.stringify({ steps: [ { From c04c42b61a789221baed98f04933e19df49f549a Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 25 Jan 2024 18:00:28 -0500 Subject: [PATCH 040/114] feat(openapi-tooling): Fix oneOf client duplications. Signed-off-by: Aramis --- .changeset/six-jobs-sin.md | 5 +++++ .changeset/tasty-feet-cheat.md | 5 +++++ .../catalog-client/src/generated/apis/DefaultApi.client.ts | 5 ++++- packages/catalog-client/src/generated/apis/index.ts | 2 +- packages/catalog-client/src/generated/index.ts | 2 +- .../generated/models/AnalyzeLocationEntityField.model.ts | 6 +++++- .../models/AnalyzeLocationExistingEntity.model.ts | 5 ++++- .../models/AnalyzeLocationGenerateEntity.model.ts | 5 ++++- .../src/generated/models/AnalyzeLocationRequest.model.ts | 5 ++++- .../src/generated/models/AnalyzeLocationResponse.model.ts | 5 ++++- .../generated/models/CreateLocation201Response.model.ts | 5 ++++- .../src/generated/models/CreateLocationRequest.model.ts | 6 +++++- .../src/generated/models/EntitiesBatchResponse.model.ts | 5 ++++- .../src/generated/models/EntitiesQueryResponse.model.ts | 5 ++++- .../models/EntitiesQueryResponsePageInfo.model.ts | 6 +++++- .../catalog-client/src/generated/models/Entity.model.ts | 5 ++++- .../src/generated/models/EntityAncestryResponse.model.ts | 5 ++++- .../models/EntityAncestryResponseItemsInner.model.ts | 5 ++++- .../src/generated/models/EntityFacet.model.ts | 6 +++++- .../src/generated/models/EntityFacetsResponse.model.ts | 5 ++++- .../src/generated/models/EntityLink.model.ts | 6 +++++- .../src/generated/models/EntityMeta.model.ts | 6 +++++- .../src/generated/models/EntityRelation.model.ts | 6 +++++- .../src/generated/models/ErrorError.model.ts | 6 +++++- .../src/generated/models/ErrorRequest.model.ts | 6 +++++- .../src/generated/models/ErrorResponse.model.ts | 6 +++++- .../src/generated/models/GetEntitiesByRefsRequest.model.ts | 6 +++++- .../generated/models/GetLocations200ResponseInner.model.ts | 5 ++++- .../catalog-client/src/generated/models/Location.model.ts | 6 +++++- .../src/generated/models/LocationInput.model.ts | 6 +++++- .../src/generated/models/LocationSpec.model.ts | 6 +++++- .../src/generated/models/ModelError.model.ts | 6 +++++- .../src/generated/models/NullableEntity.model.ts | 5 ++++- .../src/generated/models/RecursivePartialEntity.model.ts | 5 ++++- .../generated/models/RecursivePartialEntityMeta.model.ts | 5 ++++- .../models/RecursivePartialEntityMetaAllOf.model.ts | 5 ++++- .../models/RecursivePartialEntityRelation.model.ts | 6 +++++- .../src/generated/models/RefreshEntityRequest.model.ts | 6 +++++- .../generated/models/ValidateEntity400Response.model.ts | 5 ++++- .../models/ValidateEntity400ResponseErrorsInner.model.ts | 7 ++++++- .../src/generated/models/ValidateEntityRequest.model.ts | 6 +++++- packages/catalog-client/src/generated/models/index.ts | 2 +- packages/catalog-client/src/generated/pluginId.ts | 2 +- .../templates/typescript-backstage/licenseInfo.mustache | 2 -- .../templates/typescript-backstage/model.mustache | 6 ------ .../modelGenericAdditionalProperties.mustache | 4 +--- 46 files changed, 183 insertions(+), 52 deletions(-) create mode 100644 .changeset/six-jobs-sin.md create mode 100644 .changeset/tasty-feet-cheat.md diff --git a/.changeset/six-jobs-sin.md b/.changeset/six-jobs-sin.md new file mode 100644 index 0000000000..307190ae81 --- /dev/null +++ b/.changeset/six-jobs-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': patch +--- + +Internal updates to auto-generated files. diff --git a/.changeset/tasty-feet-cheat.md b/.changeset/tasty-feet-cheat.md new file mode 100644 index 0000000000..1e93439fa6 --- /dev/null +++ b/.changeset/tasty-feet-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Fixes an issue where comments would be duplicated in the template. Also, removes a header with the title and version of the OpenAPI spec from generated code. diff --git a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts index 65b8699370..99ab7bc4b1 100644 --- a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts +++ b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { DiscoveryApi } from '../types/discovery'; import { FetchApi } from '../types/fetch'; import crossFetch from 'cross-fetch'; diff --git a/packages/catalog-client/src/generated/apis/index.ts b/packages/catalog-client/src/generated/apis/index.ts index ad2f72c5b2..51dcca33fe 100644 --- a/packages/catalog-client/src/generated/apis/index.ts +++ b/packages/catalog-client/src/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/generated/index.ts b/packages/catalog-client/src/generated/index.ts index 0cf3e9cc49..bb399e97a0 100644 --- a/packages/catalog-client/src/generated/index.ts +++ b/packages/catalog-client/src/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts index a1c1e1f669..08fd73a0e6 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface AnalyzeLocationEntityField { /** * A text to show to the user to inform about the choices made. Like, it could say \"Found a CODEOWNERS file that covers this target, so we suggest leaving this field empty; which would currently make it owned by X\" where X is taken from the codeowners file. diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts index 5ede0fb60b..058a4a9f29 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { Entity } from '../models/Entity.model'; import { LocationSpec } from '../models/LocationSpec.model'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts index 886aac05d9..b65b7ef29d 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model'; import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts index e8e9542f5a..bd70c9b2ba 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { LocationInput } from '../models/LocationInput.model'; export interface AnalyzeLocationRequest { diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts index bf60aa8018..ffde7ec576 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model'; import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model'; diff --git a/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts index d090d80bf2..763c956c76 100644 --- a/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts +++ b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { Entity } from '../models/Entity.model'; import { Location } from '../models/Location.model'; diff --git a/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts index 6ef950072e..596879e519 100644 --- a/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts +++ b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface CreateLocationRequest { target: string; type: string; diff --git a/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts index d09b2cc086..a7085db16c 100644 --- a/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { NullableEntity } from '../models/NullableEntity.model'; export interface EntitiesBatchResponse { diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts index 10d4747caf..106176890b 100644 --- a/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model'; import { Entity } from '../models/Entity.model'; diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts index 8a7d5b39bb..f257d89675 100644 --- a/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface EntitiesQueryResponsePageInfo { /** * The cursor for the next batch of entities. diff --git a/packages/catalog-client/src/generated/models/Entity.model.ts b/packages/catalog-client/src/generated/models/Entity.model.ts index 59ef9bc741..0c8c1347bf 100644 --- a/packages/catalog-client/src/generated/models/Entity.model.ts +++ b/packages/catalog-client/src/generated/models/Entity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityMeta } from '../models/EntityMeta.model'; import { EntityRelation } from '../models/EntityRelation.model'; diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts index 08076fcf36..9e0369659a 100644 --- a/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model'; export interface EntityAncestryResponse { diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts index 7713c21295..b03ea1e5ef 100644 --- a/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { Entity } from '../models/Entity.model'; export interface EntityAncestryResponseItemsInner { diff --git a/packages/catalog-client/src/generated/models/EntityFacet.model.ts b/packages/catalog-client/src/generated/models/EntityFacet.model.ts index 3872091d77..76e058568a 100644 --- a/packages/catalog-client/src/generated/models/EntityFacet.model.ts +++ b/packages/catalog-client/src/generated/models/EntityFacet.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface EntityFacet { value: string; count: number; diff --git a/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts index 0ff37e4f2f..777499b6af 100644 --- a/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityFacet } from '../models/EntityFacet.model'; export interface EntityFacetsResponse { diff --git a/packages/catalog-client/src/generated/models/EntityLink.model.ts b/packages/catalog-client/src/generated/models/EntityLink.model.ts index f52f9f2df0..ae56e9dab4 100644 --- a/packages/catalog-client/src/generated/models/EntityLink.model.ts +++ b/packages/catalog-client/src/generated/models/EntityLink.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + /** * A link to external information that is related to the entity. */ diff --git a/packages/catalog-client/src/generated/models/EntityMeta.model.ts b/packages/catalog-client/src/generated/models/EntityMeta.model.ts index cb0fd28fdc..72f9f66e0d 100644 --- a/packages/catalog-client/src/generated/models/EntityMeta.model.ts +++ b/packages/catalog-client/src/generated/models/EntityMeta.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityLink } from '../models/EntityLink.model'; /** @@ -21,6 +24,7 @@ import { EntityLink } from '../models/EntityLink.model'; */ export interface EntityMeta { [key: string]: any; + /** * A list of external hyperlinks related to the entity. */ diff --git a/packages/catalog-client/src/generated/models/EntityRelation.model.ts b/packages/catalog-client/src/generated/models/EntityRelation.model.ts index 1107f7563c..5a52707369 100644 --- a/packages/catalog-client/src/generated/models/EntityRelation.model.ts +++ b/packages/catalog-client/src/generated/models/EntityRelation.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + /** * A relation of a specific type to another entity in the catalog. */ diff --git a/packages/catalog-client/src/generated/models/ErrorError.model.ts b/packages/catalog-client/src/generated/models/ErrorError.model.ts index 9dbfa22813..2459bd3954 100644 --- a/packages/catalog-client/src/generated/models/ErrorError.model.ts +++ b/packages/catalog-client/src/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface ErrorError { name: string; message: string; diff --git a/packages/catalog-client/src/generated/models/ErrorRequest.model.ts b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts index e6306b87f5..8d52f0ff4e 100644 --- a/packages/catalog-client/src/generated/models/ErrorRequest.model.ts +++ b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface ErrorRequest { method: string; url: string; diff --git a/packages/catalog-client/src/generated/models/ErrorResponse.model.ts b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts index 1f55c34b4a..0efd2893e6 100644 --- a/packages/catalog-client/src/generated/models/ErrorResponse.model.ts +++ b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface ErrorResponse { statusCode: number; } diff --git a/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts index bdda7c2fa6..5d07c5c172 100644 --- a/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts +++ b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface GetEntitiesByRefsRequest { entityRefs: Array; fields?: Array; diff --git a/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts index 134306cce7..db21eeda95 100644 --- a/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts +++ b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { Location } from '../models/Location.model'; export interface GetLocations200ResponseInner { diff --git a/packages/catalog-client/src/generated/models/Location.model.ts b/packages/catalog-client/src/generated/models/Location.model.ts index 27e47a5a25..44f69d85b0 100644 --- a/packages/catalog-client/src/generated/models/Location.model.ts +++ b/packages/catalog-client/src/generated/models/Location.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + /** * Entity location for a specific entity. */ diff --git a/packages/catalog-client/src/generated/models/LocationInput.model.ts b/packages/catalog-client/src/generated/models/LocationInput.model.ts index 8de7e3e6c9..0468e1a40a 100644 --- a/packages/catalog-client/src/generated/models/LocationInput.model.ts +++ b/packages/catalog-client/src/generated/models/LocationInput.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface LocationInput { type: string; target: string; diff --git a/packages/catalog-client/src/generated/models/LocationSpec.model.ts b/packages/catalog-client/src/generated/models/LocationSpec.model.ts index 80db2cadf2..15826e5fec 100644 --- a/packages/catalog-client/src/generated/models/LocationSpec.model.ts +++ b/packages/catalog-client/src/generated/models/LocationSpec.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + /** * Holds the entity location information. */ diff --git a/packages/catalog-client/src/generated/models/ModelError.model.ts b/packages/catalog-client/src/generated/models/ModelError.model.ts index 989e755c9a..862398915d 100644 --- a/packages/catalog-client/src/generated/models/ModelError.model.ts +++ b/packages/catalog-client/src/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,16 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { ErrorError } from '../models/ErrorError.model'; import { ErrorRequest } from '../models/ErrorRequest.model'; import { ErrorResponse } from '../models/ErrorResponse.model'; export interface ModelError { [key: string]: any; + error: ErrorError; request?: ErrorRequest; response: ErrorResponse; diff --git a/packages/catalog-client/src/generated/models/NullableEntity.model.ts b/packages/catalog-client/src/generated/models/NullableEntity.model.ts index 36aff86ba8..c9945dcb39 100644 --- a/packages/catalog-client/src/generated/models/NullableEntity.model.ts +++ b/packages/catalog-client/src/generated/models/NullableEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityMeta } from '../models/EntityMeta.model'; import { EntityRelation } from '../models/EntityRelation.model'; diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts index 8de7824550..490485e9bb 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { RecursivePartialEntityMeta } from '../models/RecursivePartialEntityMeta.model'; import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntityRelation.model'; diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts index b21cc1bb21..58f895c96a 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityLink } from '../models/EntityLink.model'; export interface RecursivePartialEntityMeta { diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts index 5111cd7f9f..d74267080e 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { EntityLink } from '../models/EntityLink.model'; /** diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts index 50635b69f4..b715b84049 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + /** * A relation of a specific type to another entity in the catalog. */ diff --git a/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts index 573523385b..78da2eb45a 100644 --- a/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts +++ b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + /** * Options for requesting a refresh of entities in the catalog. */ diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts index 0031b2edc2..e6f824104d 100644 --- a/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts +++ b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ import { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model'; export interface ValidateEntity400Response { diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts index 6223df27af..9088c7fcf4 100644 --- a/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts +++ b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,13 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface ValidateEntity400ResponseErrorsInner { [key: string]: any; + name: string; message: string; } diff --git a/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts index d5ce1be5c9..414ffb5733 100644 --- a/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts +++ b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * NOTE: This class is auto generated, do not edit the class manually. + */ + export interface ValidateEntityRequest { location: string; entity: { [key: string]: any }; diff --git a/packages/catalog-client/src/generated/models/index.ts b/packages/catalog-client/src/generated/models/index.ts index 5a93dbb33a..d6c81becb5 100644 --- a/packages/catalog-client/src/generated/models/index.ts +++ b/packages/catalog-client/src/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/generated/pluginId.ts b/packages/catalog-client/src/generated/pluginId.ts index c8c8ce1ca3..b45379f0b0 100644 --- a/packages/catalog-client/src/generated/pluginId.ts +++ b/packages/catalog-client/src/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache b/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache index afc90f6227..fa54b15df0 100644 --- a/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache +++ b/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache @@ -1,7 +1,5 @@ // /** - * {{{appName}}}{{#version}}@{{{.}}}{{/version}} - * * NOTE: This class is auto generated, do not edit the class manually. */ \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/model.mustache b/packages/repo-tools/templates/typescript-backstage/model.mustache index 97d7858cb2..565eecb4e8 100644 --- a/packages/repo-tools/templates/typescript-backstage/model.mustache +++ b/packages/repo-tools/templates/typescript-backstage/model.mustache @@ -6,12 +6,6 @@ import { {{classname}} } from '{{filename}}.model'; {{/tsImports}} - -{{#description}} -/** - * {{{.}}} - */ -{{/description}} {{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#isAlias}}{{>modelAlias}}{{/isAlias}}{{^isAlias}}{{#taggedUnions}}{{>modelTaggedUnion}}{{/taggedUnions}}{{^taggedUnions}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/taggedUnions}}{{/isAlias}}{{/isEnum}} {{/model}} {{/models}} diff --git a/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache b/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache index b8d7d1ceb7..e1e94cea80 100644 --- a/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache +++ b/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache @@ -1,7 +1,5 @@ {{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericAdditionalProperties.mustache }} {{#additionalPropertiesType}} - - [key: string]: {{{additionalPropertiesType}}}{{#hasVars}} | any{{/hasVars}}; - + [key: string]: {{{additionalPropertiesType}}}; {{/additionalPropertiesType}} \ No newline at end of file From fed57c3b1eb241264c3ee76a99f1cf675aa46369 Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 25 Jan 2024 18:04:39 -0500 Subject: [PATCH 041/114] update the autogenerated file comment Signed-off-by: Aramis --- .../templates/typescript-backstage/licenseInfo.mustache | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache b/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache index fa54b15df0..7a1b10e780 100644 --- a/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache +++ b/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache @@ -1,5 +1,5 @@ // -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ \ No newline at end of file +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** \ No newline at end of file From ea76a53949c706ddb9c21b6552c1c699f45600b2 Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 25 Jan 2024 18:05:29 -0500 Subject: [PATCH 042/114] update catalog-client to use the new autogenerated comment. Signed-off-by: Aramis --- .../catalog-client/src/generated/apis/DefaultApi.client.ts | 6 +++--- .../generated/models/AnalyzeLocationEntityField.model.ts | 6 +++--- .../generated/models/AnalyzeLocationExistingEntity.model.ts | 6 +++--- .../generated/models/AnalyzeLocationGenerateEntity.model.ts | 6 +++--- .../src/generated/models/AnalyzeLocationRequest.model.ts | 6 +++--- .../src/generated/models/AnalyzeLocationResponse.model.ts | 6 +++--- .../src/generated/models/CreateLocation201Response.model.ts | 6 +++--- .../src/generated/models/CreateLocationRequest.model.ts | 6 +++--- .../src/generated/models/EntitiesBatchResponse.model.ts | 6 +++--- .../src/generated/models/EntitiesQueryResponse.model.ts | 6 +++--- .../generated/models/EntitiesQueryResponsePageInfo.model.ts | 6 +++--- .../catalog-client/src/generated/models/Entity.model.ts | 6 +++--- .../src/generated/models/EntityAncestryResponse.model.ts | 6 +++--- .../models/EntityAncestryResponseItemsInner.model.ts | 6 +++--- .../src/generated/models/EntityFacet.model.ts | 6 +++--- .../src/generated/models/EntityFacetsResponse.model.ts | 6 +++--- .../catalog-client/src/generated/models/EntityLink.model.ts | 6 +++--- .../catalog-client/src/generated/models/EntityMeta.model.ts | 6 +++--- .../src/generated/models/EntityRelation.model.ts | 6 +++--- .../catalog-client/src/generated/models/ErrorError.model.ts | 6 +++--- .../src/generated/models/ErrorRequest.model.ts | 6 +++--- .../src/generated/models/ErrorResponse.model.ts | 6 +++--- .../src/generated/models/GetEntitiesByRefsRequest.model.ts | 6 +++--- .../generated/models/GetLocations200ResponseInner.model.ts | 6 +++--- .../catalog-client/src/generated/models/Location.model.ts | 6 +++--- .../src/generated/models/LocationInput.model.ts | 6 +++--- .../src/generated/models/LocationSpec.model.ts | 6 +++--- .../catalog-client/src/generated/models/ModelError.model.ts | 6 +++--- .../src/generated/models/NullableEntity.model.ts | 6 +++--- .../src/generated/models/RecursivePartialEntity.model.ts | 6 +++--- .../generated/models/RecursivePartialEntityMeta.model.ts | 6 +++--- .../models/RecursivePartialEntityMetaAllOf.model.ts | 6 +++--- .../models/RecursivePartialEntityRelation.model.ts | 6 +++--- .../src/generated/models/RefreshEntityRequest.model.ts | 6 +++--- .../src/generated/models/ValidateEntity400Response.model.ts | 6 +++--- .../models/ValidateEntity400ResponseErrorsInner.model.ts | 6 +++--- .../src/generated/models/ValidateEntityRequest.model.ts | 6 +++--- 37 files changed, 111 insertions(+), 111 deletions(-) diff --git a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts index 99ab7bc4b1..e8e715ab32 100644 --- a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts +++ b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { DiscoveryApi } from '../types/discovery'; import { FetchApi } from '../types/fetch'; import crossFetch from 'cross-fetch'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts index 08fd73a0e6..48d2c897a7 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface AnalyzeLocationEntityField { /** diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts index 058a4a9f29..7a1417145e 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { Entity } from '../models/Entity.model'; import { LocationSpec } from '../models/LocationSpec.model'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts index b65b7ef29d..3e33c59330 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model'; import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts index bd70c9b2ba..15e9b96e2c 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { LocationInput } from '../models/LocationInput.model'; export interface AnalyzeLocationRequest { diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts index ffde7ec576..e22d45627a 100644 --- a/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model'; import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model'; diff --git a/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts index 763c956c76..367e701af8 100644 --- a/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts +++ b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { Entity } from '../models/Entity.model'; import { Location } from '../models/Location.model'; diff --git a/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts index 596879e519..badddbf60d 100644 --- a/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts +++ b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface CreateLocationRequest { target: string; diff --git a/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts index a7085db16c..3275a7b6e7 100644 --- a/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { NullableEntity } from '../models/NullableEntity.model'; export interface EntitiesBatchResponse { diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts index 106176890b..9950dd98d1 100644 --- a/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model'; import { Entity } from '../models/Entity.model'; diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts index f257d89675..4d2a0af058 100644 --- a/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface EntitiesQueryResponsePageInfo { /** diff --git a/packages/catalog-client/src/generated/models/Entity.model.ts b/packages/catalog-client/src/generated/models/Entity.model.ts index 0c8c1347bf..f4a30fbf66 100644 --- a/packages/catalog-client/src/generated/models/Entity.model.ts +++ b/packages/catalog-client/src/generated/models/Entity.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityMeta } from '../models/EntityMeta.model'; import { EntityRelation } from '../models/EntityRelation.model'; diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts index 9e0369659a..4e0ead7d3b 100644 --- a/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model'; export interface EntityAncestryResponse { diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts index b03ea1e5ef..22b59e201f 100644 --- a/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { Entity } from '../models/Entity.model'; export interface EntityAncestryResponseItemsInner { diff --git a/packages/catalog-client/src/generated/models/EntityFacet.model.ts b/packages/catalog-client/src/generated/models/EntityFacet.model.ts index 76e058568a..aa637501f4 100644 --- a/packages/catalog-client/src/generated/models/EntityFacet.model.ts +++ b/packages/catalog-client/src/generated/models/EntityFacet.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface EntityFacet { value: string; diff --git a/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts index 777499b6af..33aa505a64 100644 --- a/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts +++ b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityFacet } from '../models/EntityFacet.model'; export interface EntityFacetsResponse { diff --git a/packages/catalog-client/src/generated/models/EntityLink.model.ts b/packages/catalog-client/src/generated/models/EntityLink.model.ts index ae56e9dab4..9de21c1b10 100644 --- a/packages/catalog-client/src/generated/models/EntityLink.model.ts +++ b/packages/catalog-client/src/generated/models/EntityLink.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** /** * A link to external information that is related to the entity. diff --git a/packages/catalog-client/src/generated/models/EntityMeta.model.ts b/packages/catalog-client/src/generated/models/EntityMeta.model.ts index 72f9f66e0d..10cda7f226 100644 --- a/packages/catalog-client/src/generated/models/EntityMeta.model.ts +++ b/packages/catalog-client/src/generated/models/EntityMeta.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityLink } from '../models/EntityLink.model'; /** diff --git a/packages/catalog-client/src/generated/models/EntityRelation.model.ts b/packages/catalog-client/src/generated/models/EntityRelation.model.ts index 5a52707369..14f7d0fab3 100644 --- a/packages/catalog-client/src/generated/models/EntityRelation.model.ts +++ b/packages/catalog-client/src/generated/models/EntityRelation.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** /** * A relation of a specific type to another entity in the catalog. diff --git a/packages/catalog-client/src/generated/models/ErrorError.model.ts b/packages/catalog-client/src/generated/models/ErrorError.model.ts index 2459bd3954..a9d3b11fdd 100644 --- a/packages/catalog-client/src/generated/models/ErrorError.model.ts +++ b/packages/catalog-client/src/generated/models/ErrorError.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface ErrorError { name: string; diff --git a/packages/catalog-client/src/generated/models/ErrorRequest.model.ts b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts index 8d52f0ff4e..be62627814 100644 --- a/packages/catalog-client/src/generated/models/ErrorRequest.model.ts +++ b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface ErrorRequest { method: string; diff --git a/packages/catalog-client/src/generated/models/ErrorResponse.model.ts b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts index 0efd2893e6..008f8b7348 100644 --- a/packages/catalog-client/src/generated/models/ErrorResponse.model.ts +++ b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface ErrorResponse { statusCode: number; diff --git a/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts index 5d07c5c172..07c6500c9f 100644 --- a/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts +++ b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface GetEntitiesByRefsRequest { entityRefs: Array; diff --git a/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts index db21eeda95..94c40d2233 100644 --- a/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts +++ b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { Location } from '../models/Location.model'; export interface GetLocations200ResponseInner { diff --git a/packages/catalog-client/src/generated/models/Location.model.ts b/packages/catalog-client/src/generated/models/Location.model.ts index 44f69d85b0..456b76ec7b 100644 --- a/packages/catalog-client/src/generated/models/Location.model.ts +++ b/packages/catalog-client/src/generated/models/Location.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** /** * Entity location for a specific entity. diff --git a/packages/catalog-client/src/generated/models/LocationInput.model.ts b/packages/catalog-client/src/generated/models/LocationInput.model.ts index 0468e1a40a..41537aaded 100644 --- a/packages/catalog-client/src/generated/models/LocationInput.model.ts +++ b/packages/catalog-client/src/generated/models/LocationInput.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface LocationInput { type: string; diff --git a/packages/catalog-client/src/generated/models/LocationSpec.model.ts b/packages/catalog-client/src/generated/models/LocationSpec.model.ts index 15826e5fec..cd9eb284f7 100644 --- a/packages/catalog-client/src/generated/models/LocationSpec.model.ts +++ b/packages/catalog-client/src/generated/models/LocationSpec.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** /** * Holds the entity location information. diff --git a/packages/catalog-client/src/generated/models/ModelError.model.ts b/packages/catalog-client/src/generated/models/ModelError.model.ts index 862398915d..7bfa165493 100644 --- a/packages/catalog-client/src/generated/models/ModelError.model.ts +++ b/packages/catalog-client/src/generated/models/ModelError.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { ErrorError } from '../models/ErrorError.model'; import { ErrorRequest } from '../models/ErrorRequest.model'; import { ErrorResponse } from '../models/ErrorResponse.model'; diff --git a/packages/catalog-client/src/generated/models/NullableEntity.model.ts b/packages/catalog-client/src/generated/models/NullableEntity.model.ts index c9945dcb39..b4344a3bfb 100644 --- a/packages/catalog-client/src/generated/models/NullableEntity.model.ts +++ b/packages/catalog-client/src/generated/models/NullableEntity.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityMeta } from '../models/EntityMeta.model'; import { EntityRelation } from '../models/EntityRelation.model'; diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts index 490485e9bb..db5ac31de3 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { RecursivePartialEntityMeta } from '../models/RecursivePartialEntityMeta.model'; import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntityRelation.model'; diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts index 58f895c96a..93e849ac99 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityLink } from '../models/EntityLink.model'; export interface RecursivePartialEntityMeta { diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts index d74267080e..7e06368102 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { EntityLink } from '../models/EntityLink.model'; /** diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts index b715b84049..9179add222 100644 --- a/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** /** * A relation of a specific type to another entity in the catalog. diff --git a/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts index 78da2eb45a..c5ab4c38eb 100644 --- a/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts +++ b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** /** * Options for requesting a refresh of entities in the catalog. diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts index e6f824104d..7cd5044782 100644 --- a/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts +++ b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** import { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model'; export interface ValidateEntity400Response { diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts index 9088c7fcf4..5617ee00bb 100644 --- a/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts +++ b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface ValidateEntity400ResponseErrorsInner { [key: string]: any; diff --git a/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts index 414ffb5733..152f28cf85 100644 --- a/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts +++ b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -/** - * NOTE: This class is auto generated, do not edit the class manually. - */ +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** export interface ValidateEntityRequest { location: string; From 701ace47b98a2fa53813d5c8bb21b8c685b1a007 Mon Sep 17 00:00:00 2001 From: Abel Rodriguez Date: Thu, 25 Jan 2024 15:14:28 -0600 Subject: [PATCH 043/114] remove unexpected changeset Signed-off-by: Abel Rodriguez --- .changeset/calm-items-double.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/calm-items-double.md b/.changeset/calm-items-double.md index 8245fb06c4..3db8ad57d9 100644 --- a/.changeset/calm-items-double.md +++ b/.changeset/calm-items-double.md @@ -1,6 +1,5 @@ --- '@backstage/plugin-scaffolder-backend-module-github': minor -'@backstage/plugin-scaffolder-backend': minor --- Allow to force the creation of a pull request from a forked repository From 20843193e8908f76c3f68c9eadb996c1a0c03f01 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 26 Jan 2024 11:44:17 +0200 Subject: [PATCH 044/114] Created Operators type and added type guard + fixed filter value to be string or number Signed-off-by: shmaram --- plugins/home/src/api/VisitsApi.ts | 16 +++++++++++++++- plugins/home/src/api/config.ts | 27 +++++++++++++++------------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/plugins/home/src/api/VisitsApi.ts b/plugins/home/src/api/VisitsApi.ts index ad750d81f2..610f4be483 100644 --- a/plugins/home/src/api/VisitsApi.ts +++ b/plugins/home/src/api/VisitsApi.ts @@ -16,6 +16,20 @@ import { createApiRef } from '@backstage/core-plugin-api'; +/** + * @public + * The operators that can be used in filter. + */ +export type Operators = '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + +/** + * @public + * Type guard for operators. + */ +export const isOperator = (s: string): s is Operators => { + return ['<', '<=', '==', '!=', '>', '>=', 'contains'].includes(s); +}; + /** * @public * Model for a visit entity. @@ -84,7 +98,7 @@ export type VisitsApiQueryParams = { */ filterBy?: Array<{ field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + operator: Operators; value: string | number; }>; }; diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 3cc6d93f24..359026958f 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Visit, VisitsApiQueryParams } from './VisitsApi'; +import { + Visit, + VisitsApiQueryParams, + Operators, + isOperator, +} from './VisitsApi'; import { Config } from '@backstage/config'; /** @@ -24,25 +29,23 @@ import { Config } from '@backstage/config'; * * @public */ + export function readFilterConfig(config: Config): | { field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + operator: Operators; value: string | number; } | undefined { try { const field = config.getString('field') as keyof Visit; - const operator = config.getString('operator') as - | '<' - | '<=' - | '==' - | '!=' - | '>' - | '>=' - | 'contains'; - const value = config.getString('value'); - return { field, operator, value }; + const operator = config.getString('operator'); + const value = + config.getOptionalNumber('value') ?? config.getString('value'); + if (isOperator(operator)) { + return { field, operator, value }; + } + return undefined; } catch (error) { // invalid filter config - ignore filter return undefined; From c47460ba98257317099bec75eaa837ac94608619 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 26 Jan 2024 12:46:03 +0200 Subject: [PATCH 045/114] fix tests + support value to be number or string Signed-off-by: shmaram --- plugins/home/src/api/config.test.ts | 10 +++++----- plugins/home/src/api/config.ts | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/plugins/home/src/api/config.test.ts b/plugins/home/src/api/config.test.ts index 178287bb3d..c21d856038 100644 --- a/plugins/home/src/api/config.test.ts +++ b/plugins/home/src/api/config.test.ts @@ -23,13 +23,13 @@ describe('config', () => { const mockConfig = new MockConfigApi({ field: 'pathname', operator: '==', - value: '3', + value: '/home', }); const res = readFilterConfig(mockConfig); expect(res).toEqual({ field: 'pathname', operator: '==', - value: '3', + value: '/home', }); }); @@ -66,11 +66,11 @@ describe('config', () => { ]); }); - it('returns only invalid filters', async () => { + it('returns only valid filters', async () => { const mockValidConfig = new MockConfigApi({ field: 'id', operator: '==', - value: '3', + value: 3, }); const mockInvalidConfig = new MockConfigApi({ myField: 'pathname', @@ -81,7 +81,7 @@ describe('config', () => { mockValidConfig, mockInvalidConfig, ]); - expect(res).toEqual([{ field: 'id', operator: '==', value: '3' }]); + expect(res).toEqual([{ field: 'id', operator: '==', value: 3 }]); }); }); }); diff --git a/plugins/home/src/api/config.ts b/plugins/home/src/api/config.ts index 359026958f..65833c13d5 100644 --- a/plugins/home/src/api/config.ts +++ b/plugins/home/src/api/config.ts @@ -40,9 +40,8 @@ export function readFilterConfig(config: Config): try { const field = config.getString('field') as keyof Visit; const operator = config.getString('operator'); - const value = - config.getOptionalNumber('value') ?? config.getString('value'); - if (isOperator(operator)) { + const value = getValue(config); + if (isOperator(operator) && value !== undefined) { return { field, operator, value }; } return undefined; @@ -52,6 +51,20 @@ export function readFilterConfig(config: Config): } } +function getValue(config: Config) { + let value = undefined; + try { + value = config.getString('value'); + } catch (error) { + try { + value = config.getNumber('value'); + } catch { + // value is not string or number - ignore filter + } + } + return value; +} + /** * Reads a set of filter by configs. * From 877efd01ca005b7a419c0b73fd0e414040a0fcb9 Mon Sep 17 00:00:00 2001 From: shmaram Date: Fri, 26 Jan 2024 12:59:55 +0200 Subject: [PATCH 046/114] update api-report.md Signed-off-by: shmaram --- plugins/home/api-report.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index dd6e22c57d..023c649ab4 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -169,6 +169,9 @@ export const homePlugin: BackstagePlugin< {} >; +// @public +export const isOperator: (s: string) => s is Operators; + // @public export type LayoutConfiguration = { component: ReactElement | string; @@ -181,6 +184,9 @@ export type LayoutConfiguration = { resizable?: boolean; }; +// @public +export type Operators = '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + // @public @deprecated (undocumented) export type RendererProps = RendererProps_2; @@ -271,7 +277,7 @@ export type VisitsApiQueryParams = { }>; filterBy?: Array<{ field: keyof Visit; - operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains'; + operator: Operators; value: string | number; }>; }; From 09f8b31f7ab7a881a9b8722fe95d4b8735ee2f54 Mon Sep 17 00:00:00 2001 From: Emmanuel Auffray Date: Fri, 26 Jan 2024 11:14:27 +0000 Subject: [PATCH 047/114] addi changeset Signed-off-by: Emmanuel Auffray --- .changeset/dry-vans-kiss.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-vans-kiss.md diff --git a/.changeset/dry-vans-kiss.md b/.changeset/dry-vans-kiss.md new file mode 100644 index 0000000000..96799240f9 --- /dev/null +++ b/.changeset/dry-vans-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +simple typo fix From 347a2b764288c5f12869c48060cd81a0f3dce27d Mon Sep 17 00:00:00 2001 From: Emmanuel Auffray Date: Fri, 26 Jan 2024 11:14:27 +0000 Subject: [PATCH 048/114] amend lazy changeset description Signed-off-by: Emmanuel Auffray --- .changeset/dry-vans-kiss.md | 2 +- .devcontainer/devcontainer.json | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json diff --git a/.changeset/dry-vans-kiss.md b/.changeset/dry-vans-kiss.md index 96799240f9..5838516b5e 100644 --- a/.changeset/dry-vans-kiss.md +++ b/.changeset/dry-vans-kiss.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -simple typo fix +Simple typo fix in the the fetch:template action example on the word skeleton diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..322317e382 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,97 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "Node.js & TypeScript", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-18-bookworm", + // "build": { + // "dockerfile": "Dockerfile" + // }, + "mounts": [ + // {"source":"${localWorkspaceFolder}","target":"/workspace","type":"bind"}, + { + "source": "${localEnv:HOME}/.ssh", + "target": "/home/node/.ssh", + "type": "bind" + }, + { + "source": "${localEnv:HOME}/.kube", + "target": "/usr/local/share/kube-localhost", + "type": "bind" + }, + { + "source": "${localEnv:HOME}/.azure", + "target": "/home/node/.azure", + "type": "bind" + } + ], + + // "remoteEnv": { + // "SYNC_LOCALHOST_KUBECONFIG": "true" + // }, + + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot", + "GitHub.copilot-chat", + "GitHub.vscode-pull-request-github", + "github.vscode-github-actions", + "Intility.vscode-backstage", + "ms-azuretools.vscode-docker", + "ms-kubernetes-tools.kind-vscode", + "ms-kubernetes-tools.vscode-kubernetes-tools", + "humao.rest-client" + ] + } + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + // Azure CLI + // "ghcr.io/devcontainers/features/azure-cli:1": { + // "installBicep": true, + // "version": "latest" + // }, + // // Docker CLI accessing the host's Docker daemon + // "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + // "moby": true, + // "installDockerBuildx": true, + // "version": "latest", + // "dockerDashComposeVersion": "v2" + // }, + // // Kubernetes CLI tools + // "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": { + // "version": "latest", + // "helm": "latest", + // "minikube": "none" + // }, + // // KinD + // "ghcr.io/devcontainers-contrib/features/kind:1": { + // "version": "latest" + // }, + // "ghcr.io/rio/features/k9s:1": {}, + // "ghcr.io/devcontainers/features/azure-cli:1": { + // "version": "latest" + // }, + // "ghcr.io/rjfmachado/devcontainer-features/cloud-native:1": { + // "kubectl": "latest", + // "helm": "latest", + // "kubelogin": "latest", + // "azwi": "latest", + // "flux": "latest", + // "cilium": "latest" + // } + } + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} From 25ab4aaf181dc22ec934141b5ab3c68529cfb989 Mon Sep 17 00:00:00 2001 From: Emmanuel Auffray Date: Fri, 26 Jan 2024 11:14:27 +0000 Subject: [PATCH 049/114] refine changeset and signoff Signed-off-by: Emmanuel Auffray --- .changeset/dry-vans-kiss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/dry-vans-kiss.md b/.changeset/dry-vans-kiss.md index 5838516b5e..031f31e912 100644 --- a/.changeset/dry-vans-kiss.md +++ b/.changeset/dry-vans-kiss.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -Simple typo fix in the the fetch:template action example on the word skeleton +Simple typo fix in the the fetch:template action example on the word 'skeleton'. From 1e61ad374a58b85ffb295817fa66fa2f4a3c0683 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 26 Jan 2024 13:13:45 +0100 Subject: [PATCH 050/114] frontend-plugin-api: no longer wrap component extension components in an ExtensionBoundary Signed-off-by: Patrik Oldsberg --- .changeset/strong-lobsters-hide.md | 5 +++ .../extensions/createComponentExtension.tsx | 34 +++++++++---------- 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 .changeset/strong-lobsters-hide.md diff --git a/.changeset/strong-lobsters-hide.md b/.changeset/strong-lobsters-hide.md new file mode 100644 index 0000000000..7b51355eb7 --- /dev/null +++ b/.changeset/strong-lobsters-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +App component extensions are no longer wrapped in an `ExtensionBoundary`, allowing them to inherit the outer context instead. diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index 2efa106750..a1231d8353 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { lazy, ComponentType } from 'react'; +import { lazy, ComponentType } from 'react'; import { AnyExtensionInputMap, ResolvedExtensionInputs, @@ -23,7 +23,7 @@ import { } from '../wiring'; import { Expand } from '../types'; import { PortableSchema } from '../schema'; -import { ExtensionBoundary, ComponentRef } from '../components'; +import { ComponentRef } from '../components'; /** @public */ export function createComponentExtension< @@ -61,28 +61,26 @@ export function createComponentExtension< output: { component: createComponentExtension.componentDataRef, }, - factory({ config, inputs, node }) { - let ExtensionComponent: ComponentType; - + factory({ config, inputs }) { if ('sync' in options.loader) { - ExtensionComponent = options.loader.sync({ config, inputs }); - } else { - const lazyLoader = options.loader.lazy; - ExtensionComponent = lazy(() => - lazyLoader({ config, inputs }).then(Component => ({ - default: Component, - })), - ) as unknown as ComponentType; + return { + component: { + ref: options.ref, + impl: options.loader.sync({ config, inputs }) as ComponentType, + }, + }; } + const lazyLoader = options.loader.lazy; + const ExtensionComponent = lazy(() => + lazyLoader({ config, inputs }).then(Component => ({ + default: Component, + })), + ) as unknown as ComponentType; return { component: { ref: options.ref, - impl: props => ( - - - - ), + impl: ExtensionComponent, }, }; }, From fb9b5e7bec0ab015fb0b3072c0968da19b54606a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 26 Jan 2024 13:14:38 +0100 Subject: [PATCH 051/114] frontend-app-api: key components by id instead of ref + tests Signed-off-by: Patrik Oldsberg --- .changeset/smart-numbers-call.md | 5 + .../DefaultComponentsApi.test.tsx | 111 ++++++++++++++++++ ...mponentsApi.ts => DefaultComponentsApi.ts} | 28 ++++- .../implementations/ComponentsApi/index.ts | 2 +- .../src/tree/resolveAppNodeSpecs.ts | 17 ++- .../frontend-app-api/src/wiring/createApp.tsx | 16 +-- 6 files changed, 153 insertions(+), 26 deletions(-) create mode 100644 .changeset/smart-numbers-call.md create mode 100644 packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx rename packages/frontend-app-api/src/apis/implementations/ComponentsApi/{ComponentsApi.ts => DefaultComponentsApi.ts} (58%) diff --git a/.changeset/smart-numbers-call.md b/.changeset/smart-numbers-call.md new file mode 100644 index 0000000000..24a2053259 --- /dev/null +++ b/.changeset/smart-numbers-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +The default `ComponentsApi` implementation now uses the `ComponentRef` ID as the component key, rather than the reference instance. This fixes a bug where duplicate installations of `@backstage/frontend-plugin-api` would break the app. diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx new file mode 100644 index 0000000000..7d62103727 --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx @@ -0,0 +1,111 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + coreExtensionData, + createComponentExtension, + createComponentRef, + createExtension, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; +import { resolveAppNodeSpecs } from '../../../tree/resolveAppNodeSpecs'; +import { resolveAppTree } from '../../../tree/resolveAppTree'; +import { App } from '../../../extensions/App'; +import { DefaultComponentsApi } from './DefaultComponentsApi'; +import { render, screen } from '@testing-library/react'; +import { instantiateAppNodeTree } from '../../../tree/instantiateAppNodeTree'; + +const testRefA = createComponentRef({ id: 'test.a' }); +const testRefB1 = createComponentRef({ id: 'test.b' }); +const testRefB2 = createComponentRef({ id: 'test.b' }); + +const baseOverrides = createExtensionOverrides({ + extensions: [ + App, + createExtension({ + namespace: 'app', + name: 'root', + attachTo: { id: 'app', input: 'root' }, + output: { + element: coreExtensionData.reactElement, + }, + factory() { + return { + element:
root
, + }; + }, + }), + ], +}); + +describe('DefaultComponentsApi', () => { + it('should provide components', () => { + const tree = resolveAppTree( + 'app', + resolveAppNodeSpecs({ + features: [ + baseOverrides, + createExtensionOverrides({ + extensions: [ + createComponentExtension({ + ref: testRefA, + loader: { sync: () => () =>
test.a
}, + }), + ], + }), + ], + }), + ); + instantiateAppNodeTree(tree.root); + const api = DefaultComponentsApi.fromTree(tree); + + const ComponentA = api.getComponent(testRefA); + render(); + + expect(screen.getByText('test.a')).toBeInTheDocument(); + }); + + it('should key extension refs by ID', () => { + const tree = resolveAppTree( + 'app', + resolveAppNodeSpecs({ + features: [ + baseOverrides, + createExtensionOverrides({ + extensions: [ + createComponentExtension({ + ref: testRefB1, + loader: { sync: () => () =>
test.b
}, + }), + ], + }), + ], + }), + ); + instantiateAppNodeTree(tree.root); + const api = DefaultComponentsApi.fromTree(tree); + + const ComponentB1 = api.getComponent(testRefB1); + const ComponentB2 = api.getComponent(testRefB2); + + expect(ComponentB1).toBe(ComponentB2); + + render(); + + expect(screen.getByText('test.b')).toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts similarity index 58% rename from packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts rename to packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts index 4d8bc47e24..572b6fc3c4 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts @@ -15,7 +15,12 @@ */ import { ComponentType } from 'react'; -import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api'; +import { + AppTree, + ComponentRef, + ComponentsApi, + createComponentExtension, +} from '@backstage/frontend-plugin-api'; /** * Implementation for the {@linkComponentApi} @@ -23,14 +28,29 @@ import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api'; * @internal */ export class DefaultComponentsApi implements ComponentsApi { - #components: Map, ComponentType>; + #components: Map>; - constructor(components: Map, any>) { + static fromTree(tree: AppTree) { + const componentEntries = tree.root.edges.attachments + .get('components') + ?.reduce((map, e) => { + const data = e.instance?.getData( + createComponentExtension.componentDataRef, + ); + if (data) { + map.set(data.ref.id, data.impl); + } + return map; + }, new Map()); + return new DefaultComponentsApi(componentEntries ?? new Map()); + } + + constructor(components: Map) { this.#components = components; } getComponent(ref: ComponentRef): ComponentType { - const impl = this.#components.get(ref); + const impl = this.#components.get(ref.id); if (!impl) { throw new Error(`No implementation found for component ref ${ref}`); } diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts index f04059c54f..18604f15de 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { DefaultComponentsApi } from './ComponentsApi'; +export { DefaultComponentsApi } from './DefaultComponentsApi'; diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index 8fd9d44638..f056e7a49e 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -31,17 +31,22 @@ import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/res /** @internal */ export function resolveAppNodeSpecs(options: { - features: FrontendFeature[]; - builtinExtensions: Extension[]; - parameters: Array; + features?: FrontendFeature[]; + builtinExtensions?: Extension[]; + parameters?: Array; forbidden?: Set; }): AppNodeSpec[] { - const { builtinExtensions, parameters, forbidden = new Set() } = options; + const { + builtinExtensions = [], + parameters = [], + forbidden = new Set(), + features = [], + } = options; - const plugins = options.features.filter( + const plugins = features.filter( (f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin', ); - const overrides = options.features.filter( + const overrides = features.filter( (f): f is ExtensionOverrides => f.$$type === '@backstage/ExtensionOverrides', ); diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 635e36aace..591e1cbf25 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -19,11 +19,9 @@ import { ConfigReader } from '@backstage/config'; import { AppTree, appTreeApiRef, - ComponentRef, componentsApiRef, coreExtensionData, createApiExtension, - createComponentExtension, createThemeExtension, createTranslationExtension, FrontendFeature, @@ -373,22 +371,10 @@ function createApiHolder( factory: () => routeResolutionApi, }); - const componentsExtensions = - tree.root.edges.attachments - .get('components') - ?.map(e => e.instance?.getData(createComponentExtension.componentDataRef)) - .filter(x => !!x) ?? []; - - const componentsMap = componentsExtensions.reduce( - (components, component) => - component ? components.set(component.ref, component?.impl) : components, - new Map, any>(), - ); - factoryRegistry.register('static', { api: componentsApiRef, deps: {}, - factory: () => new DefaultComponentsApi(componentsMap), + factory: () => DefaultComponentsApi.fromTree(tree), }); factoryRegistry.register('static', { From 53e682b82356f21ad5cf44f39c0d18e71ee58748 Mon Sep 17 00:00:00 2001 From: Frank Showalter <842058+fshowalter@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:03:58 -0500 Subject: [PATCH 052/114] Update react18-migration.md Fix a typo on the type resolution instructions. Signed-off-by: Frank Showalter <842058+fshowalter@users.noreply.github.com> --- docs/tutorials/react18-migration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/react18-migration.md b/docs/tutorials/react18-migration.md index 2d3b3a961a..c11bc83a7e 100644 --- a/docs/tutorials/react18-migration.md +++ b/docs/tutorials/react18-migration.md @@ -21,9 +21,9 @@ To switch a project to React 18, there are generally three changes that need to // highlight-remove-next-line "@types/react": "^17", // highlight-remove-next-line - "@types/react": "^17", + "@types/react-dom": "^17", // highlight-add-next-line - "@types/react-dom": "^18", + "@types/react": "^18", // highlight-add-next-line "@types/react-dom": "^18", }, From b58673ef61abf7061b2a930670b075ab2acf05e4 Mon Sep 17 00:00:00 2001 From: Kamil Markow Date: Fri, 26 Jan 2024 08:46:02 -0500 Subject: [PATCH 053/114] Upgrade jest Signed-off-by: Kamil Markow --- .changeset/green-flies-draw.md | 5 +++++ packages/cli/package.json | 4 ++-- yarn.lock | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/green-flies-draw.md diff --git a/.changeset/green-flies-draw.md b/.changeset/green-flies-draw.md new file mode 100644 index 0000000000..6b967feceb --- /dev/null +++ b/.changeset/green-flies-draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Upgrade jest diff --git a/packages/cli/package.json b/packages/cli/package.json index 5ca7d675d3..12c00565d2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -62,7 +62,7 @@ "@swc/core": "^1.3.46", "@swc/helpers": "^0.5.0", "@swc/jest": "^0.2.22", - "@types/jest": "^29.0.0", + "@types/jest": "^29.5.11", "@types/webpack-env": "^1.15.2", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.7.2", @@ -100,7 +100,7 @@ "handlebars": "^4.7.3", "html-webpack-plugin": "^5.3.1", "inquirer": "^8.2.0", - "jest": "^29.0.2", + "jest": "^29.7.0", "jest-css-modules": "^2.1.0", "jest-environment-jsdom": "^29.0.2", "jest-runtime": "^29.0.2", diff --git a/yarn.lock b/yarn.lock index 23bb660897..e9686c3676 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3586,7 +3586,7 @@ __metadata: "@types/fs-extra": ^9.0.1 "@types/http-proxy": ^1.17.4 "@types/inquirer": ^8.1.3 - "@types/jest": ^29.0.0 + "@types/jest": ^29.5.11 "@types/minimatch": ^5.0.0 "@types/node": ^18.17.8 "@types/npm-packlist": ^3.0.0 @@ -3635,7 +3635,7 @@ __metadata: handlebars: ^4.7.3 html-webpack-plugin: ^5.3.1 inquirer: ^8.2.0 - jest: ^29.0.2 + jest: ^29.7.0 jest-css-modules: ^2.1.0 jest-environment-jsdom: ^29.0.2 jest-runtime: ^29.0.2 @@ -18213,7 +18213,7 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:*, @types/jest@npm:^29.0.0": +"@types/jest@npm:*, @types/jest@npm:^29.0.0, @types/jest@npm:^29.5.11": version: 29.5.11 resolution: "@types/jest@npm:29.5.11" dependencies: @@ -31347,7 +31347,7 @@ __metadata: languageName: node linkType: hard -"jest@npm:^29.0.2": +"jest@npm:^29.7.0": version: 29.7.0 resolution: "jest@npm:29.7.0" dependencies: From 68cc58ffefb134ce2c0239cc21560ee8c1e7b710 Mon Sep 17 00:00:00 2001 From: Mitchell Hentges Date: Fri, 26 Jan 2024 16:30:54 +0100 Subject: [PATCH 054/114] Address minor nits in new frontend system docs Fix some small typos, correct some casing, and add a link to an area that I struggled to understand without guidance. Signed-off-by: Mitchell Hentges --- docs/frontend-system/architecture/03-extensions.md | 8 ++++---- docs/frontend-system/architecture/06-utility-apis.md | 2 +- docs/frontend-system/architecture/07-routes.md | 2 +- docs/frontend-system/building-plugins/02-testing.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 6d86415817..46bfa3d86f 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -114,7 +114,7 @@ const extension = createExtension({ ### Extension Data Uniqueness -Note that the key used in the output map, in this case `element`, is only used internally within the definition of the extension itself. That actual identifier for the data when consumed by other extensions is the ID of the reference, in this case `core.reactElement`. This means that you can not output multiple different values for the same extension data reference, as they would conflict with each other. That in turn makes overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. +Note that the key used in the output map, in this case `element`, is only used internally within the definition of the extension itself. That actual identifier for the data when consumed by other extensions is the ID of the reference, in this case [`core.reactElement`](https://github.com/backstage/backstage/blob/916da47e8abdb880877daa18881eb8fdbb33e70a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts#L23). This means that you can not output multiple different values for the same extension data reference, as they would conflict with each other. That in turn makes overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. ```tsx const extension = createExtension({ @@ -160,7 +160,7 @@ const extension = createExtension({ ## Extension Inputs -The Extension Data can be passed up to other extensions through their extension inputs. Similar to the outputs seen before, let's create an example an extension with a extension input: +The Extension Data can be passed up to other extensions through their extension inputs. Similar to the outputs seen before, let's create an example of an extension with a extension input: ```tsx const navigationExtension = createExtension({ @@ -254,7 +254,7 @@ With the `inputs` not only the `output` of an extensions item is passed to the e ## Extension Configuration -With the `app-config.yaml` there is already the option to pass configuration to plugins or the app to e.g. define the `baseURL` of your app. For extensions this concept would be limiting as an extension can be independent of the plugin & initiated several times. Therefor we created a possibility to configure each extension individually through config. The extension config schema is created using the [`zod`](https://zod.dev/) library, which in addition to TypeScript type checking also provides runtime validation and coercion. If we continue with the example of the `navigationExtension` and now want it to contain a configurable title, we could make it available like the following: +With the `app-config.yaml` there is already the option to pass configuration to plugins or the app to e.g. define the `baseURL` of your app. For extensions this concept would be limiting as an extension can be independent of the plugin & initiated several times. Therefore we created a possibility to configure each extension individually through config. The extension config schema is created using the [`zod`](https://zod.dev/) library, which in addition to TypeScript type checking also provides runtime validation and coercion. If we continue with the example of the `navigationExtension` and now want it to contain a configurable title, we could make it available like the following: ```tsx const navigationExtension = createExtension({ @@ -297,7 +297,7 @@ app: With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. -If we follow the example from above all items of the navigation have similarities, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefor `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. +If we follow the example from above all items of the navigation have similarities, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefore `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. ```tsx export const HomeNavIcon = createNavItemExtension({ diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index fe714793d7..49e5854b65 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -14,7 +14,7 @@ Utility APIs are pieces of standalone functionality, interfaces that can be requ A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. -The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depended on each other. The app also chooses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. +The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depend on each other. The app also chooses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. ![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg) diff --git a/docs/frontend-system/architecture/07-routes.md b/docs/frontend-system/architecture/07-routes.md index 11586573be..7fb053aa56 100644 --- a/docs/frontend-system/architecture/07-routes.md +++ b/docs/frontend-system/architecture/07-routes.md @@ -66,7 +66,7 @@ export default createPlugin({ }); ``` -In the example above we associated the `indexRouteRef` with the `catalogIndexPage` extension and provided both the route ref and page via the Catalog plugin. So, When this plugin is installed in the app, the index page will become associated with the newly created `RouteRef`, making it possible to use the route ref to navigate the page extension. +In the example above we associated the `indexRouteRef` with the `catalogIndexPage` extension and provided both the route ref and page via the Catalog plugin. So, when this plugin is installed in the app, the index page will become associated with the newly created `RouteRef`, making it possible to use the route ref to navigate the page extension. It may seem unclear why we configure the `routes` option when creating a plugin as the route has already been passed to the extension. We do that to make it possible for other plugins to route to our page, which is explained in detail in the [binding routes](#binding-external-route-references) section. diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md index c6621d6584..fa29918311 100644 --- a/docs/frontend-system/building-plugins/02-testing.md +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -70,7 +70,7 @@ describe('Entity details component', () => { await renderInTestApp( - + , ); From 60ba48c5916d425f54667a3d0566635e961a1606 Mon Sep 17 00:00:00 2001 From: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:06:50 -0500 Subject: [PATCH 055/114] chore(docs): Improve API reference wording Signed-off-by: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> --- packages/catalog-model/src/kinds/relations.ts | 2 +- packages/test-utils/src/testUtils/TestApiProvider.tsx | 4 ++-- plugins/explore/src/api/ExploreApi.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/catalog-model/src/kinds/relations.ts b/packages/catalog-model/src/kinds/relations.ts index 4d545c9d35..69f9aae1c4 100644 --- a/packages/catalog-model/src/kinds/relations.ts +++ b/packages/catalog-model/src/kinds/relations.ts @@ -114,7 +114,7 @@ export const RELATION_CHILD_OF = 'childOf'; export const RELATION_MEMBER_OF = 'memberOf'; /** - * A relation from a group to its member, typcally a user in a group. Reversed direction of + * A relation from a group to its member, typically a user in a group. Reversed direction of * {@link RELATION_MEMBER_OF}. * * @public diff --git a/packages/test-utils/src/testUtils/TestApiProvider.tsx b/packages/test-utils/src/testUtils/TestApiProvider.tsx index c212b94bea..59fafbeadb 100644 --- a/packages/test-utils/src/testUtils/TestApiProvider.tsx +++ b/packages/test-utils/src/testUtils/TestApiProvider.tsx @@ -93,8 +93,8 @@ export class TestApiRegistry implements ApiHolder { * * @remarks * todo: remove this remark tag and ship in the api-reference. There's some odd formatting going on when this is made into a markdown doc, that there's no line break between - * the emmited

for To the following

so what happens is that when parsing in docusaurus, it thinks that the code block is mdx rather than a code - * snippet. Just ommiting this from the report for now until we can work out how to fix laterr. + * the emitted

for To the following

so what happens is that when parsing in docusaurus, it thinks that the code block is mdx rather than a code + * snippet. Just omitting this from the report for now until we can work out how to fix later. * A migration from `ApiRegistry` and `ApiProvider` might look like this, from: * * ```tsx diff --git a/plugins/explore/src/api/ExploreApi.ts b/plugins/explore/src/api/ExploreApi.ts index ef9eb646b7..e5d8c7efa6 100644 --- a/plugins/explore/src/api/ExploreApi.ts +++ b/plugins/explore/src/api/ExploreApi.ts @@ -29,7 +29,7 @@ export interface ExploreApi { /** * Returns a list of explore tools. * - * @param request - The The request query options + * @param request - The request query options */ getTools(request?: GetExploreToolsRequest): Promise; } From 07e7d12db6e72d85617aecfe233326327cbe9144 Mon Sep 17 00:00:00 2001 From: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:08:10 -0500 Subject: [PATCH 056/114] chore: Add changeset Signed-off-by: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> --- .changeset/hot-paws-tap.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/hot-paws-tap.md diff --git a/.changeset/hot-paws-tap.md b/.changeset/hot-paws-tap.md new file mode 100644 index 0000000000..30d8b396ba --- /dev/null +++ b/.changeset/hot-paws-tap.md @@ -0,0 +1,7 @@ +--- +'@backstage/catalog-model': patch +'@backstage/test-utils': patch +'@backstage/plugin-explore': patch +--- + +Fix wording in API reference From 281e8c676d3104a1195787ef82acc11d390b4196 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 26 Jan 2024 17:47:54 +0100 Subject: [PATCH 057/114] core-components: remove SidebarIntro and IntroCard Signed-off-by: Patrik Oldsberg --- .changeset/four-mugs-try.md | 5 + packages/core-components/api-report.md | 18 -- .../src/layout/Sidebar/Intro.tsx | 199 ------------------ .../src/layout/Sidebar/Sidebar.stories.tsx | 3 - .../src/layout/Sidebar/index.ts | 2 - .../src/overridableComponents.ts | 2 - 6 files changed, 5 insertions(+), 224 deletions(-) create mode 100644 .changeset/four-mugs-try.md delete mode 100644 packages/core-components/src/layout/Sidebar/Intro.tsx diff --git a/.changeset/four-mugs-try.md b/.changeset/four-mugs-try.md new file mode 100644 index 0000000000..11fdf783d4 --- /dev/null +++ b/.changeset/four-mugs-try.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +**BREAKING**: Removed the `SidebarIntro` component as it was providing instructions for features that do not exist, along with `IntroCard`. If you were relying on this component and want to keep using it you can refer to the original implementations of [`SidebarIntro`](https://github.com/backstage/backstage/blob/80f2413334ed9b221ec3c2b7c22fa737ad8d8885/packages/core-components/src/layout/Sidebar/Intro.tsx#L149) and [`IntroCard`](https://github.com/backstage/backstage/blob/80f2413334ed9b221ec3c2b7c22fa737ad8d8885/packages/core-components/src/layout/Sidebar/Intro.tsx#L100). diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index cca7017089..b5d54dd606 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -593,11 +593,6 @@ export type InfoCardClassKey = // @public (undocumented) export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; -// Warning: (ae-forgotten-export) The symbol "IntroCardProps" needs to be exported by the entry point index.d.ts -// -// @public -export function IntroCard(props: IntroCardProps): React_2.JSX.Element; - // Warning: (ae-forgotten-export) The symbol "ItemCardProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ItemCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -953,19 +948,6 @@ export interface SidebarGroupProps extends BottomNavigationActionProps { to?: string; } -// Warning: (ae-missing-release-tag) "SidebarIntro" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function SidebarIntro(_props: {}): React_2.JSX.Element | null; - -// @public (undocumented) -export type SidebarIntroClassKey = - | 'introCard' - | 'introDismiss' - | 'introDismissLink' - | 'introDismissText' - | 'introDismissIcon'; - // Warning: (ae-forgotten-export) The symbol "SidebarItemProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "SidebarItem" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx deleted file mode 100644 index 6f75f096fb..0000000000 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ /dev/null @@ -1,199 +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 Box from '@material-ui/core/Box'; -import Collapse from '@material-ui/core/Collapse'; -import IconButton from '@material-ui/core/IconButton'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; -import CloseIcon from '@material-ui/icons/Close'; -import { useLocalStorageValue } from '@react-hookz/web'; -import React, { useContext, useState } from 'react'; - -import { - SIDEBAR_INTRO_LOCAL_STORAGE, - SidebarConfig, - SidebarConfigContext, -} from './config'; -import { SidebarDivider } from './Items'; -import { useSidebarOpenState } from './SidebarOpenStateContext'; - -/** @public */ -export type SidebarIntroClassKey = - | 'introCard' - | 'introDismiss' - | 'introDismissLink' - | 'introDismissText' - | 'introDismissIcon'; - -const useStyles = makeStyles( - theme => ({ - introCard: props => ({ - color: '#b5b5b5', - // XXX (@koroeskohr): should I be using a Mui theme variable? - fontSize: 12, - width: props.sidebarConfig.drawerWidthOpen, - marginTop: theme.spacing(2.25), - marginBottom: theme.spacing(1.5), - paddingLeft: props.sidebarConfig.iconPadding, - paddingRight: props.sidebarConfig.iconPadding, - }), - introDismiss: { - display: 'flex', - justifyContent: 'flex-end', - alignItems: 'center', - marginTop: theme.spacing(1.5), - }, - introDismissLink: { - color: '#dddddd', - display: 'flex', - alignItems: 'center', - marginBottom: theme.spacing(0.5), - '&:hover': { - color: theme.palette.linkHover, - transition: theme.transitions.create('color', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.shortest, - }), - }, - }, - introDismissText: { - fontSize: '0.7rem', - fontWeight: 'bold', - textTransform: 'uppercase', - letterSpacing: 1, - }, - introDismissIcon: { - width: 18, - height: 18, - marginRight: theme.spacing(1.5), - }, - }), - { name: 'BackstageSidebarIntro' }, -); - -type IntroCardProps = { - text: string; - onClose: () => void; -}; - -/** - * Closable card with information from Navigation Sidebar - * - * @public - * - */ - -export function IntroCard(props: IntroCardProps) { - const { sidebarConfig } = useContext(SidebarConfigContext); - const classes = useStyles({ sidebarConfig }); - const { text, onClose } = props; - const handleClose = () => onClose(); - - return ( - - {text} - - - - - Dismiss - - - - - ); -} - -type SidebarIntroLocalStorage = { - starredItemsDismissed: boolean; - recentlyViewedItemsDismissed: boolean; -}; - -type SidebarIntroCardProps = { - text: string; - onDismiss: () => void; -}; - -const SidebarIntroCard = (props: SidebarIntroCardProps) => { - const { text, onDismiss } = props; - const [collapsing, setCollapsing] = useState(false); - const startDismissing = () => { - setCollapsing(true); - }; - return ( - - - - ); -}; - -const starredIntroText = `Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav. -Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!`; -const recentlyViewedIntroText = - 'And your recently viewed plugins will pop up here!'; - -export function SidebarIntro(_props: {}) { - const { isOpen } = useSidebarOpenState(); - const defaultValue = { - starredItemsDismissed: false, - recentlyViewedItemsDismissed: false, - }; - const { value: dismissedIntro, set: setDismissedIntro } = - useLocalStorageValue(SIDEBAR_INTRO_LOCAL_STORAGE); - - const { starredItemsDismissed, recentlyViewedItemsDismissed } = - dismissedIntro ?? {}; - - const dismissStarred = () => { - setDismissedIntro(state => ({ - ...defaultValue, - ...state, - starredItemsDismissed: true, - })); - }; - const dismissRecentlyViewed = () => { - setDismissedIntro(state => ({ - ...defaultValue, - ...state, - recentlyViewedItemsDismissed: true, - })); - }; - - if (!isOpen) { - return null; - } - - return ( - <> - {!starredItemsDismissed && ( - <> - - - - )} - {!recentlyViewedItemsDismissed && ( - - )} - - ); -} diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx index 342a9b2a29..aef837b646 100644 --- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx @@ -34,7 +34,6 @@ import { SidebarSearchField, SidebarSpace, } from './Items'; -import { SidebarIntro } from './Intro'; import { SidebarSubmenu } from './SidebarSubmenu'; import { SidebarSubmenuItem } from './SidebarSubmenuItem'; @@ -65,7 +64,6 @@ export const SampleSidebar = () => ( - @@ -104,7 +102,6 @@ export const SampleScalableSidebar = () => ( - diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index 4c91ea6a8f..b614d50feb 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -44,8 +44,6 @@ export type { SidebarSpacerClassKey, SidebarDividerClassKey, } from './Items'; -export { IntroCard, SidebarIntro } from './Intro'; -export type { SidebarIntroClassKey } from './Intro'; export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config'; export type { SidebarOptions, SubmenuOptions } from './config'; export { diff --git a/packages/core-components/src/overridableComponents.ts b/packages/core-components/src/overridableComponents.ts index 750303e4df..85e9dc96a8 100644 --- a/packages/core-components/src/overridableComponents.ts +++ b/packages/core-components/src/overridableComponents.ts @@ -83,7 +83,6 @@ import { SidebarSpaceClassKey, SidebarSpacerClassKey, SidebarDividerClassKey, - SidebarIntroClassKey, SidebarItemClassKey, SidebarPageClassKey, CustomProviderClassKey, @@ -157,7 +156,6 @@ type BackstageComponentsNameToClassKey = { BackstageSidebarSpace: SidebarSpaceClassKey; BackstageSidebarSpacer: SidebarSpacerClassKey; BackstageSidebarDivider: SidebarDividerClassKey; - BackstageSidebarIntro: SidebarIntroClassKey; BackstageSidebarItem: SidebarItemClassKey; BackstageSidebarPage: SidebarPageClassKey; BackstageCustomProvider: CustomProviderClassKey; From 3dc36fae11dfef0566d0d38444e2da51ee37e077 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:25:58 +0000 Subject: [PATCH 058/114] chore(deps): update dependency esbuild to v0.19.12 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 190 +++++++++++++++++++++++++++--------------------------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5a15adff92..65dacde577 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10470,9 +10470,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/aix-ppc64@npm:0.19.11" +"@esbuild/aix-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/aix-ppc64@npm:0.19.12" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -10491,9 +10491,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/android-arm64@npm:0.19.11" +"@esbuild/android-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm64@npm:0.19.12" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -10512,9 +10512,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/android-arm@npm:0.19.11" +"@esbuild/android-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm@npm:0.19.12" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -10533,9 +10533,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/android-x64@npm:0.19.11" +"@esbuild/android-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-x64@npm:0.19.12" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -10554,9 +10554,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/darwin-arm64@npm:0.19.11" +"@esbuild/darwin-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-arm64@npm:0.19.12" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -10575,9 +10575,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/darwin-x64@npm:0.19.11" +"@esbuild/darwin-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-x64@npm:0.19.12" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -10596,9 +10596,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/freebsd-arm64@npm:0.19.11" +"@esbuild/freebsd-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-arm64@npm:0.19.12" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -10617,9 +10617,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/freebsd-x64@npm:0.19.11" +"@esbuild/freebsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-x64@npm:0.19.12" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -10638,9 +10638,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-arm64@npm:0.19.11" +"@esbuild/linux-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm64@npm:0.19.12" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -10659,9 +10659,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-arm@npm:0.19.11" +"@esbuild/linux-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm@npm:0.19.12" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -10680,9 +10680,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-ia32@npm:0.19.11" +"@esbuild/linux-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ia32@npm:0.19.12" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -10701,9 +10701,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-loong64@npm:0.19.11" +"@esbuild/linux-loong64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-loong64@npm:0.19.12" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -10722,9 +10722,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-mips64el@npm:0.19.11" +"@esbuild/linux-mips64el@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-mips64el@npm:0.19.12" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -10743,9 +10743,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-ppc64@npm:0.19.11" +"@esbuild/linux-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ppc64@npm:0.19.12" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -10764,9 +10764,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-riscv64@npm:0.19.11" +"@esbuild/linux-riscv64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-riscv64@npm:0.19.12" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -10785,9 +10785,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-s390x@npm:0.19.11" +"@esbuild/linux-s390x@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-s390x@npm:0.19.12" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -10806,9 +10806,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-x64@npm:0.19.11" +"@esbuild/linux-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-x64@npm:0.19.12" conditions: os=linux & cpu=x64 languageName: node linkType: hard @@ -10827,9 +10827,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/netbsd-x64@npm:0.19.11" +"@esbuild/netbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/netbsd-x64@npm:0.19.12" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard @@ -10848,9 +10848,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/openbsd-x64@npm:0.19.11" +"@esbuild/openbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/openbsd-x64@npm:0.19.12" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -10869,9 +10869,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/sunos-x64@npm:0.19.11" +"@esbuild/sunos-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/sunos-x64@npm:0.19.12" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -10890,9 +10890,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/win32-arm64@npm:0.19.11" +"@esbuild/win32-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-arm64@npm:0.19.12" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -10911,9 +10911,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/win32-ia32@npm:0.19.11" +"@esbuild/win32-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-ia32@npm:0.19.12" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -10932,9 +10932,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/win32-x64@npm:0.19.11" +"@esbuild/win32-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-x64@npm:0.19.12" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -25767,32 +25767,32 @@ __metadata: linkType: hard "esbuild@npm:^0.19.0, esbuild@npm:~0.19.10": - version: 0.19.11 - resolution: "esbuild@npm:0.19.11" + version: 0.19.12 + resolution: "esbuild@npm:0.19.12" dependencies: - "@esbuild/aix-ppc64": 0.19.11 - "@esbuild/android-arm": 0.19.11 - "@esbuild/android-arm64": 0.19.11 - "@esbuild/android-x64": 0.19.11 - "@esbuild/darwin-arm64": 0.19.11 - "@esbuild/darwin-x64": 0.19.11 - "@esbuild/freebsd-arm64": 0.19.11 - "@esbuild/freebsd-x64": 0.19.11 - "@esbuild/linux-arm": 0.19.11 - "@esbuild/linux-arm64": 0.19.11 - "@esbuild/linux-ia32": 0.19.11 - "@esbuild/linux-loong64": 0.19.11 - "@esbuild/linux-mips64el": 0.19.11 - "@esbuild/linux-ppc64": 0.19.11 - "@esbuild/linux-riscv64": 0.19.11 - "@esbuild/linux-s390x": 0.19.11 - "@esbuild/linux-x64": 0.19.11 - "@esbuild/netbsd-x64": 0.19.11 - "@esbuild/openbsd-x64": 0.19.11 - "@esbuild/sunos-x64": 0.19.11 - "@esbuild/win32-arm64": 0.19.11 - "@esbuild/win32-ia32": 0.19.11 - "@esbuild/win32-x64": 0.19.11 + "@esbuild/aix-ppc64": 0.19.12 + "@esbuild/android-arm": 0.19.12 + "@esbuild/android-arm64": 0.19.12 + "@esbuild/android-x64": 0.19.12 + "@esbuild/darwin-arm64": 0.19.12 + "@esbuild/darwin-x64": 0.19.12 + "@esbuild/freebsd-arm64": 0.19.12 + "@esbuild/freebsd-x64": 0.19.12 + "@esbuild/linux-arm": 0.19.12 + "@esbuild/linux-arm64": 0.19.12 + "@esbuild/linux-ia32": 0.19.12 + "@esbuild/linux-loong64": 0.19.12 + "@esbuild/linux-mips64el": 0.19.12 + "@esbuild/linux-ppc64": 0.19.12 + "@esbuild/linux-riscv64": 0.19.12 + "@esbuild/linux-s390x": 0.19.12 + "@esbuild/linux-x64": 0.19.12 + "@esbuild/netbsd-x64": 0.19.12 + "@esbuild/openbsd-x64": 0.19.12 + "@esbuild/sunos-x64": 0.19.12 + "@esbuild/win32-arm64": 0.19.12 + "@esbuild/win32-ia32": 0.19.12 + "@esbuild/win32-x64": 0.19.12 dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -25842,7 +25842,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: ae949a796d1d06b55275ae7491ce137857468f69a93d8cc9c0943d2a701ac54e14dbb250a2ba56f2ad98283669578f1ec3bd85a4681910a5ff29a2470c3bd62c + checksum: 2936e29107b43e65a775b78b7bc66ddd7d76febd73840ac7e825fb22b65029422ff51038a08d19b05154f543584bd3afe7d1ef1c63900429475b17fbe61cb61f languageName: node linkType: hard From 6021b2f437603ece1e03ef23c6fd930a24b23be7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:27:17 +0000 Subject: [PATCH 059/114] chore(deps): update dependency msw to v2.1.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5a15adff92..cb7009077e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12837,9 +12837,9 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.25.14": - version: 0.25.14 - resolution: "@mswjs/interceptors@npm:0.25.14" +"@mswjs/interceptors@npm:^0.25.15": + version: 0.25.15 + resolution: "@mswjs/interceptors@npm:0.25.15" dependencies: "@open-draft/deferred-promise": ^2.2.0 "@open-draft/logger": ^0.3.0 @@ -12847,7 +12847,7 @@ __metadata: is-node-process: ^1.2.0 outvariant: ^1.2.1 strict-event-emitter: ^0.5.1 - checksum: caf9513cf6848ff0c3f1402abf881d3c1333f68fae54076cfd3fd919b7edb709769342bd3afd2a60ef4ae698ef47776b6203e0ea6a5d8e788e97eda7ed9dbbd4 + checksum: dbe43f2df398bbe48ee5ea4ecf055144c9c5689218e5955b01378ea084044dab992d1b434d3533e75df044030a976402e53ec65d743c2619e024defd75ffc076 languageName: node linkType: hard @@ -34633,13 +34633,13 @@ __metadata: linkType: hard "msw@npm:^2.0.0, msw@npm:^2.0.8": - version: 2.1.4 - resolution: "msw@npm:2.1.4" + version: 2.1.5 + resolution: "msw@npm:2.1.5" dependencies: "@bundled-es-modules/cookie": ^2.0.0 "@bundled-es-modules/statuses": ^1.0.1 "@mswjs/cookies": ^1.1.0 - "@mswjs/interceptors": ^0.25.14 + "@mswjs/interceptors": ^0.25.15 "@open-draft/until": ^2.1.0 "@types/cookie": ^0.6.0 "@types/statuses": ^2.0.4 @@ -34661,7 +34661,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: da8aaf9682ac48a635966beef9add9493297de797b266066bcd8ae0c2708488b81558251412e41489511a63deda1774b42e28197e9b73ddf14d9ecf8bb916e7a + checksum: 19a54a25baa584d1bafa219e1c2245073688eeed6ea5330f27c868a2f279390ee4fbd8a13d64cf0a056149d7e2faa300f901959bab8854e010f29368524d7c4f languageName: node linkType: hard From 69df041760ae02c25a4526513fb3f698da1721ba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:01:59 +0000 Subject: [PATCH 060/114] chore(deps): update github/codeql-action action to v3.23.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/verify_codeql.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 73dd5f8c57..d8dd0d9a6a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -66,6 +66,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/upload-sarif@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 245ef401fa..ddf6d2cf28 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@v3.23.1 + uses: github/codeql-action/upload-sarif@v3.23.2 with: sarif_file: snyk.sarif diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 6fbf516c08..df0dd80118 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3.23.1 + uses: github/codeql-action/init@v3.23.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3.23.1 + uses: github/codeql-action/autobuild@v3.23.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.23.1 + uses: github/codeql-action/analyze@v3.23.2 From 04c936b686d04d16f76b37fa109bd6e06acf572a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:02:25 +0000 Subject: [PATCH 061/114] chore(deps): update microsoft/setup-msbuild action to v1.3.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/verify_e2e-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 395dbe0b7f..802247341f 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -61,7 +61,7 @@ jobs: python-version: '3.10' - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.3.1 + uses: microsoft/setup-msbuild@v1.3.2 - name: Setup gyp env run: | From 08804c361ccc0e05b6daedd704ce78401fd5ab0e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 26 Jan 2024 18:18:54 +0100 Subject: [PATCH 062/114] cli: fix module discovery race Signed-off-by: Patrik Oldsberg --- .changeset/two-geese-explain.md | 5 +++++ packages/cli/package.json | 1 + .../cli/src/lib/bundler/packageDetection.ts | 18 ++++++++++++------ yarn.lock | 1 + 4 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 .changeset/two-geese-explain.md diff --git a/.changeset/two-geese-explain.md b/.changeset/two-geese-explain.md new file mode 100644 index 0000000000..12ae307cfe --- /dev/null +++ b/.changeset/two-geese-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed an issue that would cause an invalid `__backstage-autodetected-plugins__.js` to be written when using experimental module discovery. diff --git a/packages/cli/package.json b/packages/cli/package.json index 5ca7d675d3..96dc2e5db8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -112,6 +112,7 @@ "node-libs-browser": "^2.2.1", "npm-packlist": "^5.0.0", "ora": "^5.3.0", + "p-queue": "^6.6.2", "postcss": "^8.1.0", "process": "^0.11.10", "react-dev-utils": "^12.0.0-next.60", diff --git a/packages/cli/src/lib/bundler/packageDetection.ts b/packages/cli/src/lib/bundler/packageDetection.ts index 3aa0955c06..0c357fe79e 100644 --- a/packages/cli/src/lib/bundler/packageDetection.ts +++ b/packages/cli/src/lib/bundler/packageDetection.ts @@ -18,6 +18,7 @@ import { BackstagePackageJson } from '@backstage/cli-node'; import { Config, ConfigReader } from '@backstage/config'; import chokidar from 'chokidar'; import fs from 'fs-extra'; +import PQueue from 'p-queue'; import { join as joinPath, resolve as resolvePath } from 'path'; import { paths as cliPaths } from '../paths'; @@ -104,6 +105,9 @@ async function detectPackages( }); } +// Make sure we're not issuing multiple writes at the same time, which can cause partial overwrites +const writeQueue = new PQueue({ concurrency: 1 }); + async function writeDetectedPackagesModule( pkgs: { name: string; export?: string; import: string }[], ) { @@ -116,13 +120,15 @@ async function writeDetectedPackagesModule( ) .join(','); - await fs.writeFile( - joinPath( - cliPaths.targetRoot, - 'node_modules', - `${DETECTED_MODULES_MODULE_NAME}.js`, + await writeQueue.add(() => + fs.writeFile( + joinPath( + cliPaths.targetRoot, + 'node_modules', + `${DETECTED_MODULES_MODULE_NAME}.js`, + ), + `window['__@backstage/discovered__'] = { modules: [${requirePackageScript}] };`, ), - `window['__@backstage/discovered__'] = { modules: [${requirePackageScript}] };`, ); } diff --git a/yarn.lock b/yarn.lock index 1c83813e10..ad55841387 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3649,6 +3649,7 @@ __metadata: nodemon: ^3.0.1 npm-packlist: ^5.0.0 ora: ^5.3.0 + p-queue: ^6.6.2 postcss: ^8.1.0 process: ^0.11.10 react-dev-utils: ^12.0.0-next.60 From 31f0a0a2da7c6484588b9cb32356b2aa139d7f12 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 20 Jan 2024 10:19:51 -0600 Subject: [PATCH 063/114] Added context menu to scaffolder pages Signed-off-by: Andre Wanlin --- .changeset/quick-penguins-refuse.md | 6 +++++ plugins/scaffolder-react/api-report-alpha.md | 1 + .../ScaffolderPageContextMenu.tsx | 19 ++++++++++++++- .../components/ActionsPage/ActionsPage.tsx | 24 +++++++++++++++++-- .../ListTasksPage/ListTasksPage.tsx | 19 +++++++++++++-- .../TemplateEditorPage/TemplateEditorPage.tsx | 23 +++++++++++++++++- 6 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 .changeset/quick-penguins-refuse.md diff --git a/.changeset/quick-penguins-refuse.md b/.changeset/quick-penguins-refuse.md new file mode 100644 index 0000000000..c76686ae75 --- /dev/null +++ b/.changeset/quick-penguins-refuse.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages# diff --git a/plugins/scaffolder-react/api-report-alpha.md b/plugins/scaffolder-react/api-report-alpha.md index 6976951670..a521fecc70 100644 --- a/plugins/scaffolder-react/api-report-alpha.md +++ b/plugins/scaffolder-react/api-report-alpha.md @@ -129,6 +129,7 @@ export type ScaffolderPageContextMenuProps = { onEditorClicked?: () => void; onActionsClicked?: () => void; onTasksClicked?: () => void; + onCreateClicked?: () => void; }; // @alpha diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx index 8326b361e1..2d132473a8 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx @@ -20,6 +20,7 @@ import ListItemText from '@material-ui/core/ListItemText'; import MenuItem from '@material-ui/core/MenuItem'; import MenuList from '@material-ui/core/MenuList'; import Popover from '@material-ui/core/Popover'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import { makeStyles } from '@material-ui/core/styles'; import Description from '@material-ui/icons/Description'; import Edit from '@material-ui/icons/Edit'; @@ -40,6 +41,7 @@ export type ScaffolderPageContextMenuProps = { onEditorClicked?: () => void; onActionsClicked?: () => void; onTasksClicked?: () => void; + onCreateClicked?: () => void; }; /** @@ -48,7 +50,8 @@ export type ScaffolderPageContextMenuProps = { export function ScaffolderPageContextMenu( props: ScaffolderPageContextMenuProps, ) { - const { onEditorClicked, onActionsClicked, onTasksClicked } = props; + const { onEditorClicked, onActionsClicked, onTasksClicked, onCreateClicked } = + props; const classes = useStyles(); const [anchorEl, setAnchorEl] = useState(); @@ -89,6 +92,20 @@ export function ScaffolderPageContextMenu( transformOrigin={{ vertical: 'top', horizontal: 'right' }} > + {onCreateClicked && ( + + + + + + + )} + {/* + + + + + */} {onEditorClicked && ( diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 5e7f9810c5..9957d22456 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -41,7 +41,7 @@ import classNames from 'classnames'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExpandLessIcon from '@material-ui/icons/ExpandLess'; -import { useApi } from '@backstage/core-plugin-api'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { CodeSnippet, Content, @@ -52,6 +52,13 @@ import { Progress, } from '@backstage/core-components'; import Chip from '@material-ui/core/Chip'; +import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; +import { useNavigate } from 'react-router-dom'; +import { + editRouteRef, + rootRouteRef, + scaffolderListTaskRouteRef, +} from '../../routes'; const useStyles = makeStyles(theme => ({ code: { @@ -109,6 +116,17 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => { export const ActionsPage = () => { const api = useApi(scaffolderApiRef); + const navigate = useNavigate(); + const editorLink = useRouteRef(editRouteRef); + const tasksLink = useRouteRef(scaffolderListTaskRouteRef); + const createLink = useRouteRef(rootRouteRef); + + const scaffolderPageContextMenuProps = { + onEditorClicked: () => navigate(editorLink()), + onActionsClicked: undefined, + onTasksClicked: () => navigate(tasksLink()), + onCreateClicked: () => navigate(createLink()), + }; const classes = useStyles(); const { loading, value, error } = useAsync(async () => { return api.listActions(); @@ -326,7 +344,9 @@ export const ActionsPage = () => { pageTitleOverride="Create a New Component" title="Installed actions" subtitle="This is the collection of all installed actions" - /> + > + + {items} ); diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index aac0ed5c79..6a764eaa1f 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -38,7 +38,9 @@ import { TaskStatusColumn, TemplateTitleColumn, } from './columns'; -import { rootRouteRef } from '../../routes'; +import { actionsRouteRef, editRouteRef, rootRouteRef } from '../../routes'; +import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; +import { useNavigate } from 'react-router-dom'; export interface MyTaskPageProps { initiallySelectedFilter?: 'owned' | 'all'; @@ -134,13 +136,26 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { }; export const ListTasksPage = (props: MyTaskPageProps) => { + const navigate = useNavigate(); + const editorLink = useRouteRef(editRouteRef); + const actionsLink = useRouteRef(actionsRouteRef); + const createLink = useRouteRef(rootRouteRef); + + const scaffolderPageContextMenuProps = { + onEditorClicked: () => navigate(editorLink()), + onActionsClicked: () => navigate(actionsLink()), + onTasksClicked: () => undefined, + onCreateClicked: () => navigate(createLink()), + }; return (
+ > + +
diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx index aab693e590..d8145c4646 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx @@ -27,6 +27,14 @@ import { type LayoutOptions, } from '@backstage/plugin-scaffolder-react'; import { TemplateEditorIntro } from './TemplateEditorIntro'; +import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; +import { useNavigate } from 'react-router-dom'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { + actionsRouteRef, + rootRouteRef, + scaffolderListTaskRouteRef, +} from '../../routes'; type Selection = | { @@ -48,6 +56,17 @@ interface TemplateEditorPageProps { export function TemplateEditorPage(props: TemplateEditorPageProps) { const [selection, setSelection] = useState(); + const navigate = useNavigate(); + const actionsLink = useRouteRef(actionsRouteRef); + const tasksLink = useRouteRef(scaffolderListTaskRouteRef); + const createLink = useRouteRef(rootRouteRef); + + const scaffolderPageContextMenuProps = { + onEditorClicked: undefined, + onActionsClicked: () => navigate(actionsLink()), + onTasksClicked: () => navigate(tasksLink()), + onCreateClicked: () => navigate(createLink()), + }; let content: JSX.Element | null = null; if (selection?.type === 'local') { @@ -100,7 +119,9 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) {
+ > + +
{content}
); From b734c9c83d9aaa564ad59963db1b207d20a63903 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 20 Jan 2024 10:21:09 -0600 Subject: [PATCH 064/114] Fixed typo Signed-off-by: Andre Wanlin --- .changeset/quick-penguins-refuse.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-penguins-refuse.md b/.changeset/quick-penguins-refuse.md index c76686ae75..5d3165114b 100644 --- a/.changeset/quick-penguins-refuse.md +++ b/.changeset/quick-penguins-refuse.md @@ -3,4 +3,4 @@ '@backstage/plugin-scaffolder': patch --- -Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages# +Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages From a9507252ad4480d7b56792ac592c2cfd974f9303 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 20 Jan 2024 10:26:13 -0600 Subject: [PATCH 065/114] Fix task list Signed-off-by: Andre Wanlin --- .../scaffolder/src/components/ListTasksPage/ListTasksPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index 6a764eaa1f..8cd8ccefda 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -144,7 +144,7 @@ export const ListTasksPage = (props: MyTaskPageProps) => { const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), onActionsClicked: () => navigate(actionsLink()), - onTasksClicked: () => undefined, + onTasksClicked: undefined, onCreateClicked: () => navigate(createLink()), }; return ( From 30c212f11b5d8047735b057fa3d30909bf13e231 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 26 Jan 2024 13:09:50 -0600 Subject: [PATCH 066/114] Removed commented out code Signed-off-by: Andre Wanlin --- .../ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx index 2d132473a8..2239c86e06 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx @@ -100,12 +100,6 @@ export function ScaffolderPageContextMenu(
)} - {/* - - - - - */} {onEditorClicked && ( From bc907df2204bf8d1821156a42046a8f8ae516774 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:27:31 +0000 Subject: [PATCH 067/114] fix(deps): update dependency @codemirror/view to v6.23.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 42811acc21..d3484630d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10179,13 +10179,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.23.0 - resolution: "@codemirror/view@npm:6.23.0" + version: 6.23.1 + resolution: "@codemirror/view@npm:6.23.1" dependencies: "@codemirror/state": ^6.4.0 style-mod: ^4.1.0 w3c-keyname: ^2.2.4 - checksum: 6e5f2314a3da2c724dc6a525654d949d3f2fcf7009f4d85f980d52ddc885c8969717e903ca1d9132afbe7c524af5d19bff8285fd394106282a965ae83aa47db4 + checksum: 5ea3ba5761c574e1f6e1f1058cb452189c890982a77991606d0ae40da3c6fff77f7c7fc3c43fa78d62677ccdfa65dbc56175706b793e34ad4ec7a63b21e8c18e languageName: node linkType: hard From 0504b78251e235405933aec1e1642fe931583127 Mon Sep 17 00:00:00 2001 From: Emmanuel Auffray Date: Fri, 26 Jan 2024 20:28:54 +0000 Subject: [PATCH 068/114] Cleanup my own typo and remove devcontainer Signed-off-by: Emmanuel Auffray --- .changeset/dry-vans-kiss.md | 2 +- .devcontainer/devcontainer.json | 97 --------------------------------- 2 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 .devcontainer/devcontainer.json diff --git a/.changeset/dry-vans-kiss.md b/.changeset/dry-vans-kiss.md index 031f31e912..5d11df989c 100644 --- a/.changeset/dry-vans-kiss.md +++ b/.changeset/dry-vans-kiss.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -Simple typo fix in the the fetch:template action example on the word 'skeleton'. +Simple typo fix in the fetch:template action example on the word 'skeleton'. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 322317e382..0000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,97 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node -{ - "name": "Node.js & TypeScript", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-18-bookworm", - // "build": { - // "dockerfile": "Dockerfile" - // }, - "mounts": [ - // {"source":"${localWorkspaceFolder}","target":"/workspace","type":"bind"}, - { - "source": "${localEnv:HOME}/.ssh", - "target": "/home/node/.ssh", - "type": "bind" - }, - { - "source": "${localEnv:HOME}/.kube", - "target": "/usr/local/share/kube-localhost", - "type": "bind" - }, - { - "source": "${localEnv:HOME}/.azure", - "target": "/home/node/.azure", - "type": "bind" - } - ], - - // "remoteEnv": { - // "SYNC_LOCALHOST_KUBECONFIG": "true" - // }, - - "customizations": { - "vscode": { - "extensions": [ - "GitHub.copilot", - "GitHub.copilot-chat", - "GitHub.vscode-pull-request-github", - "github.vscode-github-actions", - "Intility.vscode-backstage", - "ms-azuretools.vscode-docker", - "ms-kubernetes-tools.kind-vscode", - "ms-kubernetes-tools.vscode-kubernetes-tools", - "humao.rest-client" - ] - } - }, - - // Features to add to the dev container. More info: https://containers.dev/features. - "features": { - // Azure CLI - // "ghcr.io/devcontainers/features/azure-cli:1": { - // "installBicep": true, - // "version": "latest" - // }, - // // Docker CLI accessing the host's Docker daemon - // "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { - // "moby": true, - // "installDockerBuildx": true, - // "version": "latest", - // "dockerDashComposeVersion": "v2" - // }, - // // Kubernetes CLI tools - // "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": { - // "version": "latest", - // "helm": "latest", - // "minikube": "none" - // }, - // // KinD - // "ghcr.io/devcontainers-contrib/features/kind:1": { - // "version": "latest" - // }, - // "ghcr.io/rio/features/k9s:1": {}, - // "ghcr.io/devcontainers/features/azure-cli:1": { - // "version": "latest" - // }, - // "ghcr.io/rjfmachado/devcontainer-features/cloud-native:1": { - // "kubectl": "latest", - // "helm": "latest", - // "kubelogin": "latest", - // "azwi": "latest", - // "flux": "latest", - // "cilium": "latest" - // } - } - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "yarn install", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} From 3c184afa918bc95841a0e3779eb3897283111e43 Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Fri, 26 Jan 2024 19:09:50 -0500 Subject: [PATCH 069/114] refactor: extract common dialog component Signed-off-by: Jamie Klassen --- .changeset/quiet-donkeys-punch.md | 5 + plugins/kubernetes-react/api-report.md | 2 +- .../KubernetesDialog/KubernetesDialog.tsx | 110 ++++++++++++++++++ .../src/components/KubernetesDialog/index.ts | 16 +++ .../PodExecTerminal/PodExecTerminalDialog.tsx | 87 +++----------- .../components/Pods/PodLogs/PodLogsDialog.tsx | 72 ++---------- 6 files changed, 157 insertions(+), 135 deletions(-) create mode 100644 .changeset/quiet-donkeys-punch.md create mode 100644 plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx create mode 100644 plugins/kubernetes-react/src/components/KubernetesDialog/index.ts diff --git a/.changeset/quiet-donkeys-punch.md b/.changeset/quiet-donkeys-punch.md new file mode 100644 index 0000000000..a375ed148c --- /dev/null +++ b/.changeset/quiet-donkeys-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +Extracted common dialog component. diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index 7e9c2fbd61..8a0ec4de8f 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -685,7 +685,7 @@ export const PodExecTerminal: ( // @public export const PodExecTerminalDialog: ( props: PodExecTerminalProps, -) => React_2.JSX.Element; +) => false | React_2.JSX.Element | undefined; // @public export interface PodExecTerminalProps { diff --git a/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx b/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx new file mode 100644 index 0000000000..cd5802f464 --- /dev/null +++ b/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx @@ -0,0 +1,110 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + Theme, + createStyles, + makeStyles, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import React, { useState } from 'react'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + dialogPaper: { minHeight: 'calc(100% - 64px)' }, + dialogContent: { flexBasis: 0 }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + }), +); + +export interface KubernetesDialogProps { + buttonAriaLabel: string; + buttonIcon: React.ReactNode; + buttonText: string; + children?: React.ReactNode; + disabled?: boolean; + title: string; +} + +/** + * Dialog component for use in the Kubernetes plugin + * + * @public + */ +export const KubernetesDialog = ({ + buttonAriaLabel, + buttonIcon, + buttonText, + children, + disabled, + title, +}: KubernetesDialogProps) => { + const classes = useStyles(); + + const [open, setOpen] = useState(false); + const openDialog = () => { + setOpen(true); + }; + const closeDialog = () => { + setOpen(false); + }; + + return ( + <> + + + {title} + + + + + + {children} + + + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/KubernetesDialog/index.ts b/plugins/kubernetes-react/src/components/KubernetesDialog/index.ts new file mode 100644 index 0000000000..1b10c1346e --- /dev/null +++ b/plugins/kubernetes-react/src/components/KubernetesDialog/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './KubernetesDialog'; diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx index 5ed8c8c0d1..0b321aa108 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx @@ -13,98 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Button, - createStyles, - Dialog, - DialogContent, - DialogTitle, - IconButton, - makeStyles, - Theme, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; -import React, { useState } from 'react'; +import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; +import React from 'react'; + +import { KubernetesDialog } from '../KubernetesDialog'; import { useIsPodExecTerminalSupported } from '../../hooks'; import { PodExecTerminal, PodExecTerminalProps } from './PodExecTerminal'; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - dialogPaper: { minHeight: 'calc(100% - 64px)' }, - dialogContent: { flexBasis: 0 }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, - }), -); - /** * Opens a terminal connected to the given pod's container in a dialog * * @public */ export const PodExecTerminalDialog = (props: PodExecTerminalProps) => { - const classes = useStyles(); const { clusterName, containerName, podName } = props; - const [open, setOpen] = useState(false); - const isPodExecTerminalSupported = useIsPodExecTerminalSupported(); - const openDialog = () => { - setOpen(true); - }; - - const closeDialog = () => { - setOpen(false); - }; - return ( - <> - {!isPodExecTerminalSupported.loading && - isPodExecTerminalSupported.value && ( - - - {podName} - {containerName} terminal shell on cluster{' '} - {clusterName} - - - - - - - - - )} - - - + + + ) ); }; diff --git a/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx b/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx index 48fda2de2a..3ffd4d8fa0 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx @@ -13,36 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState } from 'react'; +import React from 'react'; -import { - Button, - createStyles, - Dialog, - DialogContent, - DialogTitle, - IconButton, - makeStyles, - Theme, -} from '@material-ui/core'; import SubjectIcon from '@material-ui/icons/Subject'; -import CloseIcon from '@material-ui/icons/Close'; - +import { KubernetesDialog } from '../../KubernetesDialog'; import { PodLogs } from './PodLogs'; import { ContainerScope } from './types'; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, - }), -); - /** * Props for PodLogsDialog * @@ -58,43 +36,15 @@ export interface PodLogsDialogProps { * @public */ export const PodLogsDialog = ({ containerScope }: PodLogsDialogProps) => { - const classes = useStyles(); - const [open, setOpen] = useState(false); - - const openDialog = () => { - setOpen(true); - }; - - const closeDialog = () => { - setOpen(false); - }; return ( - <> - - - {containerScope.podName} - {containerScope.containerName} logs on - cluster {containerScope.clusterName} - - - - - - - - - - + } + buttonText="Logs" + disabled={false} + title={`${containerScope.podName} - ${containerScope.containerName} logs on cluster ${containerScope.clusterName}`} + > + + ); }; From b07ec70c90331bde0473d0ffe1d3b37dfb07de0f Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 24 Jan 2024 16:04:37 +0100 Subject: [PATCH 070/114] feat(scaffolder): Use more distinguishable icons for link and text output Use more distinguishable icons for link (`Link`) and text output (`Description`). Currently, both use the same icon `Web` (until specified explicitly at the template). For a user, it is not really clear which of these will open more output below it vs open a URL/a new tab. Signed-off-by: Patrick Jungermann --- .changeset/twelve-hounds-know.md | 5 +++++ .../src/next/components/TemplateOutputs/LinkOutputs.tsx | 4 ++-- .../src/next/components/TemplateOutputs/TextOutputs.tsx | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/twelve-hounds-know.md diff --git a/.changeset/twelve-hounds-know.md b/.changeset/twelve-hounds-know.md new file mode 100644 index 0000000000..3c95c71ddb --- /dev/null +++ b/.changeset/twelve-hounds-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': minor +--- + +Use more distinguishable icons for link (`Link`) and text output (`Description`). diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx index d172aa9cb3..7877653197 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx @@ -17,7 +17,7 @@ import { IconComponent, useApp, useRouteRef } from '@backstage/core-plugin-api'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { Button, makeStyles } from '@material-ui/core'; import React from 'react'; -import WebIcon from '@material-ui/icons/Web'; +import LinkIcon from '@material-ui/icons/Link'; import { parseEntityRef } from '@backstage/catalog-model'; import { Link } from '@backstage/core-components'; import { ScaffolderTaskOutput } from '../../../api'; @@ -37,7 +37,7 @@ export const LinkOutputs = (props: { output: ScaffolderTaskOutput }) => { const entityRoute = useRouteRef(entityRouteRef); const iconResolver = (key?: string): IconComponent => - app.getSystemIcon(key!) ?? WebIcon; + app.getSystemIcon(key!) ?? LinkIcon; return ( <> diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx index 8f61aa6a30..74a5eabb8f 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx @@ -15,7 +15,7 @@ */ import { IconComponent, useApp } from '@backstage/core-plugin-api'; import { Button } from '@material-ui/core'; -import WebIcon from '@material-ui/icons/Web'; +import DescriptionIcon from '@material-ui/icons/Description'; import React from 'react'; import { ScaffolderTaskOutput } from '../../../api'; @@ -33,7 +33,7 @@ export const TextOutputs = (props: { const app = useApp(); const iconResolver = (key?: string): IconComponent => - app.getSystemIcon(key!) ?? WebIcon; + app.getSystemIcon(key!) ?? DescriptionIcon; return ( <> From 4131bd27248922c64e084ae05e9ba0ff13235f9d Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sat, 27 Jan 2024 22:59:17 +0100 Subject: [PATCH 071/114] Fix for issue 20256: Bug Report: converting circular structure to JSON error Signed-off-by: bnechyporenko --- plugins/scaffolder-react/package.json | 1 + plugins/scaffolder-react/src/next/lib/schema.ts | 3 ++- yarn.lock | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index bb038cd2ee..0f6da12d6c 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -66,6 +66,7 @@ "@types/json-schema": "^7.0.9", "@types/react": "^16.13.1 || ^17.0.0", "classnames": "^2.2.6", + "flatted": "3.2.9", "humanize-duration": "^3.25.1", "immer": "^9.0.1", "json-schema": "^0.4.0", diff --git a/plugins/scaffolder-react/src/next/lib/schema.ts b/plugins/scaffolder-react/src/next/lib/schema.ts index c606d715e7..b7c1364f41 100644 --- a/plugins/scaffolder-react/src/next/lib/schema.ts +++ b/plugins/scaffolder-react/src/next/lib/schema.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; +import { stringify, parse } from 'flatted'; import { FieldValidation, UiSchema } from '@rjsf/utils'; function isObject(value: unknown): value is JsonObject { @@ -123,7 +124,7 @@ export const extractSchemaFromStep = ( inputStep: JsonObject, ): { uiSchema: UiSchema; schema: JsonObject } => { const uiSchema: UiSchema = {}; - const returnSchema: JsonObject = JSON.parse(JSON.stringify(inputStep)); + const returnSchema: JsonObject = parse(stringify(inputStep)); extractUiSchema(returnSchema, uiSchema); return { uiSchema, schema: returnSchema }; }; diff --git a/yarn.lock b/yarn.lock index 1c83813e10..d9ffd84fca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8420,6 +8420,7 @@ __metadata: "@types/luxon": ^3.0.0 "@types/react": ^16.13.1 || ^17.0.0 classnames: ^2.2.6 + flatted: 3.2.9 humanize-duration: ^3.25.1 immer: ^9.0.1 json-schema: ^0.4.0 @@ -27530,10 +27531,10 @@ __metadata: languageName: node linkType: hard -"flatted@npm:^3.1.0": - version: 3.1.1 - resolution: "flatted@npm:3.1.1" - checksum: 508935e3366d95444131f0aaa801a4301f24ea5bcb900d12764e7335b46b910730cc1b5bcfcfb8eccb7c8db261ba0671c6a7ca30d10870ff7a7756dc7e731a7a +"flatted@npm:3.2.9, flatted@npm:^3.1.0": + version: 3.2.9 + resolution: "flatted@npm:3.2.9" + checksum: f14167fbe26a9d20f6fca8d998e8f1f41df72c8e81f9f2c9d61ed2bea058248f5e1cbd05e7f88c0e5087a6a0b822a1e5e2b446e879f3cfbe0b07ba2d7f80b026 languageName: node linkType: hard From 3f60ad55995a60ca345b7bd066db3244d7d38411 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sat, 27 Jan 2024 23:05:12 +0100 Subject: [PATCH 072/114] wip Signed-off-by: bnechyporenko --- .changeset/chilled-ways-wave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilled-ways-wave.md diff --git a/.changeset/chilled-ways-wave.md b/.changeset/chilled-ways-wave.md new file mode 100644 index 0000000000..68e4d1eb7d --- /dev/null +++ b/.changeset/chilled-ways-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +fix for: converting circular structure to JSON error From 33d2cd8f7221fb164e2704ba950f372b74de718b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:58:02 +0000 Subject: [PATCH 073/114] fix(deps): update dependency @swc/core to v1.3.107 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 86 ++++++++++++++++++++++----------------------- storybook/yarn.lock | 86 ++++++++++++++++++++++----------------------- yarn.lock | 86 ++++++++++++++++++++++----------------------- 3 files changed, 129 insertions(+), 129 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 5c92ba5e69..50465ec651 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2701,90 +2701,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-darwin-arm64@npm:1.3.105" +"@swc/core-darwin-arm64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-arm64@npm:1.3.107" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-darwin-x64@npm:1.3.105" +"@swc/core-darwin-x64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-x64@npm:1.3.107" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.105" +"@swc/core-linux-arm-gnueabihf@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.107" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.105" +"@swc/core-linux-arm64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.105" +"@swc/core-linux-arm64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.105" +"@swc/core-linux-x64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-x64-musl@npm:1.3.105" +"@swc/core-linux-x64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-musl@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.105" +"@swc/core-win32-arm64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.107" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.105" +"@swc/core-win32-ia32-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.107" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.105" +"@swc/core-win32-x64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.107" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.3.105 - resolution: "@swc/core@npm:1.3.105" + version: 1.3.107 + resolution: "@swc/core@npm:1.3.107" dependencies: - "@swc/core-darwin-arm64": 1.3.105 - "@swc/core-darwin-x64": 1.3.105 - "@swc/core-linux-arm-gnueabihf": 1.3.105 - "@swc/core-linux-arm64-gnu": 1.3.105 - "@swc/core-linux-arm64-musl": 1.3.105 - "@swc/core-linux-x64-gnu": 1.3.105 - "@swc/core-linux-x64-musl": 1.3.105 - "@swc/core-win32-arm64-msvc": 1.3.105 - "@swc/core-win32-ia32-msvc": 1.3.105 - "@swc/core-win32-x64-msvc": 1.3.105 + "@swc/core-darwin-arm64": 1.3.107 + "@swc/core-darwin-x64": 1.3.107 + "@swc/core-linux-arm-gnueabihf": 1.3.107 + "@swc/core-linux-arm64-gnu": 1.3.107 + "@swc/core-linux-arm64-musl": 1.3.107 + "@swc/core-linux-x64-gnu": 1.3.107 + "@swc/core-linux-x64-musl": 1.3.107 + "@swc/core-win32-arm64-msvc": 1.3.107 + "@swc/core-win32-ia32-msvc": 1.3.107 + "@swc/core-win32-x64-msvc": 1.3.107 "@swc/counter": ^0.1.1 "@swc/types": ^0.1.5 peerDependencies: @@ -2813,7 +2813,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 5baa880bc92748ef4845d9c65eba5d6dd01adaa673854e20a5116f5e267c12180db50e563cf3c34a415772b9742d021176a9d9a91065c190ef6f54fefe85728c + checksum: 0dccff50461fb8c0f4af053b70e555c91386cb07aa7657a7328d58e397d15640723587549416d8fa7dcc073ad11b39318146bd50ec4a82345ce2ce39c7ba4c00 languageName: node linkType: hard diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 94acb9696e..54f50cbbb5 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -2842,90 +2842,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-darwin-arm64@npm:1.3.105" +"@swc/core-darwin-arm64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-arm64@npm:1.3.107" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-darwin-x64@npm:1.3.105" +"@swc/core-darwin-x64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-x64@npm:1.3.107" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.105" +"@swc/core-linux-arm-gnueabihf@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.107" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.105" +"@swc/core-linux-arm64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.105" +"@swc/core-linux-arm64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.105" +"@swc/core-linux-x64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-x64-musl@npm:1.3.105" +"@swc/core-linux-x64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-musl@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.105" +"@swc/core-win32-arm64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.107" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.105" +"@swc/core-win32-ia32-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.107" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.105" +"@swc/core-win32-x64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.107" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.3.105 - resolution: "@swc/core@npm:1.3.105" + version: 1.3.107 + resolution: "@swc/core@npm:1.3.107" dependencies: - "@swc/core-darwin-arm64": 1.3.105 - "@swc/core-darwin-x64": 1.3.105 - "@swc/core-linux-arm-gnueabihf": 1.3.105 - "@swc/core-linux-arm64-gnu": 1.3.105 - "@swc/core-linux-arm64-musl": 1.3.105 - "@swc/core-linux-x64-gnu": 1.3.105 - "@swc/core-linux-x64-musl": 1.3.105 - "@swc/core-win32-arm64-msvc": 1.3.105 - "@swc/core-win32-ia32-msvc": 1.3.105 - "@swc/core-win32-x64-msvc": 1.3.105 + "@swc/core-darwin-arm64": 1.3.107 + "@swc/core-darwin-x64": 1.3.107 + "@swc/core-linux-arm-gnueabihf": 1.3.107 + "@swc/core-linux-arm64-gnu": 1.3.107 + "@swc/core-linux-arm64-musl": 1.3.107 + "@swc/core-linux-x64-gnu": 1.3.107 + "@swc/core-linux-x64-musl": 1.3.107 + "@swc/core-win32-arm64-msvc": 1.3.107 + "@swc/core-win32-ia32-msvc": 1.3.107 + "@swc/core-win32-x64-msvc": 1.3.107 "@swc/counter": ^0.1.1 "@swc/types": ^0.1.5 peerDependencies: @@ -2954,7 +2954,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 5baa880bc92748ef4845d9c65eba5d6dd01adaa673854e20a5116f5e267c12180db50e563cf3c34a415772b9742d021176a9d9a91065c190ef6f54fefe85728c + checksum: 0dccff50461fb8c0f4af053b70e555c91386cb07aa7657a7328d58e397d15640723587549416d8fa7dcc073ad11b39318146bd50ec4a82345ce2ce39c7ba4c00 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 6eff422ebd..bfd2c3fe95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17018,90 +17018,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-darwin-arm64@npm:1.3.105" +"@swc/core-darwin-arm64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-arm64@npm:1.3.107" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-darwin-x64@npm:1.3.105" +"@swc/core-darwin-x64@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-darwin-x64@npm:1.3.107" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.105" +"@swc/core-linux-arm-gnueabihf@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.107" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.105" +"@swc/core-linux-arm64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.105" +"@swc/core-linux-arm64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.107" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.105" +"@swc/core-linux-x64-gnu@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-linux-x64-musl@npm:1.3.105" +"@swc/core-linux-x64-musl@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-linux-x64-musl@npm:1.3.107" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.105" +"@swc/core-win32-arm64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.107" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.105" +"@swc/core-win32-ia32-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.107" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.105": - version: 1.3.105 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.105" +"@swc/core-win32-x64-msvc@npm:1.3.107": + version: 1.3.107 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.107" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.3.105 - resolution: "@swc/core@npm:1.3.105" + version: 1.3.107 + resolution: "@swc/core@npm:1.3.107" dependencies: - "@swc/core-darwin-arm64": 1.3.105 - "@swc/core-darwin-x64": 1.3.105 - "@swc/core-linux-arm-gnueabihf": 1.3.105 - "@swc/core-linux-arm64-gnu": 1.3.105 - "@swc/core-linux-arm64-musl": 1.3.105 - "@swc/core-linux-x64-gnu": 1.3.105 - "@swc/core-linux-x64-musl": 1.3.105 - "@swc/core-win32-arm64-msvc": 1.3.105 - "@swc/core-win32-ia32-msvc": 1.3.105 - "@swc/core-win32-x64-msvc": 1.3.105 + "@swc/core-darwin-arm64": 1.3.107 + "@swc/core-darwin-x64": 1.3.107 + "@swc/core-linux-arm-gnueabihf": 1.3.107 + "@swc/core-linux-arm64-gnu": 1.3.107 + "@swc/core-linux-arm64-musl": 1.3.107 + "@swc/core-linux-x64-gnu": 1.3.107 + "@swc/core-linux-x64-musl": 1.3.107 + "@swc/core-win32-arm64-msvc": 1.3.107 + "@swc/core-win32-ia32-msvc": 1.3.107 + "@swc/core-win32-x64-msvc": 1.3.107 "@swc/counter": ^0.1.1 "@swc/types": ^0.1.5 peerDependencies: @@ -17130,7 +17130,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 5baa880bc92748ef4845d9c65eba5d6dd01adaa673854e20a5116f5e267c12180db50e563cf3c34a415772b9742d021176a9d9a91065c190ef6f54fefe85728c + checksum: 0dccff50461fb8c0f4af053b70e555c91386cb07aa7657a7328d58e397d15640723587549416d8fa7dcc073ad11b39318146bd50ec4a82345ce2ce39c7ba4c00 languageName: node linkType: hard From 2e3f378eb5722afbeea614f79f0f4ad3eb1ab6e4 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 28 Jan 2024 22:03:15 +0100 Subject: [PATCH 074/114] feat: change links Signed-off-by: David Weber --- microsite/data/plugins/api-spectral-linter.yaml | 4 ++-- microsite/data/plugins/api-wsdl.yaml | 4 ++-- microsite/data/plugins/end-of-life.yaml | 4 ++-- microsite/data/plugins/tips.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/microsite/data/plugins/api-spectral-linter.yaml b/microsite/data/plugins/api-spectral-linter.yaml index 292279e324..5f1682c71b 100644 --- a/microsite/data/plugins/api-spectral-linter.yaml +++ b/microsite/data/plugins/api-spectral-linter.yaml @@ -4,7 +4,7 @@ author: dweber019 authorUrl: https://github.com/dweber019 category: Linting description: API Spectral Linter is a quality assurance tool that checks the compliance of API's specifications Spectral rule sets. -documentation: https://github.com/dweber019/backstage-plugin-api-docs-spectral-linter -iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugin-api-docs-spectral-linter/main/plugins/api-docs-spectral-linter/docs/pluginIcon.png +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/api-docs-spectral-linter +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/api-docs-spectral-linter/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-api-docs-spectral-linter' addedDate: '2023-03-27' diff --git a/microsite/data/plugins/api-wsdl.yaml b/microsite/data/plugins/api-wsdl.yaml index 6523531f1d..219227c509 100644 --- a/microsite/data/plugins/api-wsdl.yaml +++ b/microsite/data/plugins/api-wsdl.yaml @@ -4,7 +4,7 @@ author: dweber019 authorUrl: https://github.com/dweber019 category: Discovery description: An API docs extension to render WSDL / SOAP web services human readable. -documentation: https://github.com/dweber019/backstage-plugin-api-docs-module-wsdl -iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugin-api-docs-module-wsdl/main/docs/logo.png +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/api-docs-module-wsdl +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/api-docs-module-wsdl/docs/logo.png npmPackageName: '@dweber019/backstage-plugin-api-docs-module-wsdl' addedDate: '2023-12-22' diff --git a/microsite/data/plugins/end-of-life.yaml b/microsite/data/plugins/end-of-life.yaml index d54a6bc5f9..ddf4b26341 100644 --- a/microsite/data/plugins/end-of-life.yaml +++ b/microsite/data/plugins/end-of-life.yaml @@ -4,7 +4,7 @@ author: dweber019 authorUrl: https://github.com/dweber019 category: Quality description: Display end of life data for entities from endoflife.data -documentation: https://github.com/dweber019/backstage-plugin-endoflife -iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugin-endoflife/main/plugins/endoflife/docs/pluginIcon.png +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/endoflife +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/endoflife/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-endoflife' addedDate: '2024-01-18' diff --git a/microsite/data/plugins/tips.yaml b/microsite/data/plugins/tips.yaml index e58ad0fb46..03327f3e41 100644 --- a/microsite/data/plugins/tips.yaml +++ b/microsite/data/plugins/tips.yaml @@ -4,7 +4,7 @@ author: dweber019 authorUrl: https://github.com/dweber019 category: Discovery description: The tips plugin will show the end user some helpful information for entities. -documentation: https://github.com/dweber019/backstage-plugin-tips -iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugin-tips/main/plugins/tips/docs/pluginIcon.png +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/tips +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/tips/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-tips' addedDate: '2023-10-08' From 01c4c810ddf4329ec0a6705c52a9b12be198ccd4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 08:59:08 +0000 Subject: [PATCH 075/114] chore(deps): update chromaui/action digest to 05a82ad Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/verify_storybook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 1c3a5fd619..49a0c76df3 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -51,7 +51,7 @@ jobs: - run: yarn build-storybook - - uses: chromaui/action@5f2cdb26c04c0364f5c8ca3fc41627a83d6ffc8a # v10 + - uses: chromaui/action@05a82adb1e6919df177f54777e81a2ef3e312323 # v10 with: token: ${{ secrets.GITHUB_TOKEN }} # projectToken intentionally shared to allow collaborators to run Chromatic on forks From 46b63dea672ca789e16a7577fc33c542194cc25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 23 Jan 2024 09:26:38 +0100 Subject: [PATCH 076/114] add defaultTarget to ExternalRouteRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/twelve-pens-rescue.md | 7 ++ packages/app-next/app-config.yaml | 1 - .../app-next/src/examples/pagesPlugin.tsx | 4 +- .../src/convertLegacyRouteRef.ts | 3 + .../src/routing/resolveRouteBindings.test.ts | 44 ++++++++++++ .../src/routing/resolveRouteBindings.ts | 72 +++++++++++-------- packages/frontend-plugin-api/api-report.md | 1 + .../src/routing/ExternalRouteRef.ts | 15 ++++ 8 files changed, 117 insertions(+), 30 deletions(-) create mode 100644 .changeset/twelve-pens-rescue.md diff --git a/.changeset/twelve-pens-rescue.md b/.changeset/twelve-pens-rescue.md new file mode 100644 index 0000000000..9d40fb2851 --- /dev/null +++ b/.changeset/twelve-pens-rescue.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +--- + +Allow external route refs in the new system to have a `defaultTarget` pointing to a route that it'll resolve to by default if no explicit bindings were made by the adopter. diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 9cc3507fa4..a79bb866db 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -3,7 +3,6 @@ app: packages: 'all' # ✨ routes: bindings: - pages.pageX: pages.pageX catalog.viewTechDoc: techdocs.docRoot catalog.createComponent: catalog-import.importPage diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index c5069f99c3..826040a6cb 100644 --- a/packages/app-next/src/examples/pagesPlugin.tsx +++ b/packages/app-next/src/examples/pagesPlugin.tsx @@ -27,7 +27,9 @@ import { Route, Routes } from 'react-router-dom'; const indexRouteRef = createRouteRef(); const page1RouteRef = createRouteRef(); -export const externalPageXRouteRef = createExternalRouteRef(); +export const externalPageXRouteRef = createExternalRouteRef({ + defaultTarget: 'pages.pageX', +}); export const pageXRouteRef = createRouteRef(); // const page2RouteRef = createSubRouteRef({ // id: 'page2', diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.ts b/packages/core-compat-api/src/convertLegacyRouteRef.ts index 36311dc081..e6a52932ce 100644 --- a/packages/core-compat-api/src/convertLegacyRouteRef.ts +++ b/packages/core-compat-api/src/convertLegacyRouteRef.ts @@ -199,6 +199,9 @@ export function convertLegacyRouteRef( getDescription() { return legacyRefStr; }, + getDefaultTarget() { + return newRef.getDefaultTarget(); + }, setId(id: string) { newRef.setId(id); }, diff --git a/packages/frontend-app-api/src/routing/resolveRouteBindings.test.ts b/packages/frontend-app-api/src/routing/resolveRouteBindings.test.ts index d80a02e34d..9b150eb127 100644 --- a/packages/frontend-app-api/src/routing/resolveRouteBindings.test.ts +++ b/packages/frontend-app-api/src/routing/resolveRouteBindings.test.ts @@ -117,4 +117,48 @@ describe('resolveRouteBindings', () => { "Invalid config at app.routes.bindings['mySource'], 'myTarget' is not a valid route", ); }); + + it('can have default targets, but at the lowest priority', () => { + const source = createExternalRouteRef({ defaultTarget: 'target1' }); + const target1 = createRouteRef(); + const target2 = createRouteRef(); + const routesById = { + routes: new Map([ + ['target1', target1], + ['target2', target2], + ]), + externalRoutes: new Map([['source', source]]), + }; + + // defaultTarget wins only if no bind or config matches + let result = resolveRouteBindings( + () => {}, + new ConfigReader({}), + routesById, + ); + + expect(result.get(source)).toBe(target1); + + // config wins over defaultTarget + result = resolveRouteBindings( + () => {}, + new ConfigReader({ + app: { routes: { bindings: { source: 'target2' } } }, + }), + routesById, + ); + + expect(result.get(source)).toBe(target2); + + // bind wins over defaultTarget + result = resolveRouteBindings( + ({ bind }) => { + bind({ a: source }, { a: target2 }); + }, + new ConfigReader({}), + routesById, + ); + + expect(result.get(source)).toBe(target2); + }); }); diff --git a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts index 18ea9f9bd8..49e3b1a1be 100644 --- a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts +++ b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts @@ -22,6 +22,8 @@ import { import { RouteRefsById } from './collectRouteIds'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; /** * Extracts a union of the keys in a map whose value extends the given type @@ -82,6 +84,7 @@ export function resolveRouteBindings( ): Map { const result = new Map(); + // Perform callback bindings first with highest priority if (bindRoutes) { const bind: CreateAppRouteBinder = ( externalRoutes, @@ -105,37 +108,50 @@ export function resolveRouteBindings( bindRoutes({ bind }); } - const bindingsConfig = config.getOptionalConfig('app.routes.bindings'); - if (!bindingsConfig) { - return result; + // Then perform config based bindings with lower priority + const bindings = config + .getOptionalConfig('app.routes.bindings') + ?.get(); + if (bindings) { + for (const [externalRefId, targetRefId] of Object.entries(bindings)) { + if (typeof targetRefId !== 'string' || targetRefId === '') { + throw new Error( + `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string`, + ); + } + + const externalRef = routesById.externalRoutes.get(externalRefId); + if (!externalRef) { + throw new Error( + `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`, + ); + } + if (result.has(externalRef)) { + continue; + } + const targetRef = routesById.routes.get(targetRefId); + if (!targetRef) { + throw new Error( + `Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`, + ); + } + + result.set(externalRef, targetRef); + } } - const bindings = bindingsConfig.get(); - for (const [externalRefId, targetRefId] of Object.entries(bindings)) { - if (typeof targetRefId !== 'string' || targetRefId === '') { - throw new Error( - `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string`, - ); + // Finally fall back to attempting to map defaults, at lowest priority + for (const externalRef of routesById.externalRoutes.values()) { + if (!result.has(externalRef)) { + const defaultRefId = + toInternalExternalRouteRef(externalRef).getDefaultTarget(); + if (defaultRefId) { + const defaultRef = routesById.routes.get(defaultRefId); + if (defaultRef) { + result.set(externalRef, defaultRef); + } + } } - - const externalRef = routesById.externalRoutes.get(externalRefId); - if (!externalRef) { - throw new Error( - `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`, - ); - } - // Route bindings defined in config have lower priority than those defined in code - if (result.has(externalRef)) { - continue; - } - const targetRef = routesById.routes.get(targetRefId); - if (!targetRef) { - throw new Error( - `Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`, - ); - } - - result.set(externalRef, targetRef); } return result; diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 4116469c2a..28c8ea321c 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -573,6 +573,7 @@ export function createExternalRouteRef< ? (keyof TParams)[] : TParamKeys[]; optional?: TOptional; + defaultTarget?: string; }): ExternalRouteRef< keyof TParams extends never ? undefined diff --git a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts index c9aabdef27..7a754ffa7b 100644 --- a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts @@ -44,6 +44,7 @@ export interface InternalExternalRouteRef< readonly version: 'v1'; getParams(): string[]; getDescription(): string; + getDefaultTarget(): string | undefined; setId(id: string): void; } @@ -80,10 +81,15 @@ class ExternalRouteRefImpl constructor( readonly optional: boolean, readonly params: string[] = [], + readonly defaultTarget: string | undefined, creationSite: string, ) { super(params, creationSite); } + + getDefaultTarget() { + return this.defaultTarget; + } } /** @@ -115,6 +121,14 @@ export function createExternalRouteRef< * if they aren't, `useExternalRouteRef` will return `undefined`. */ optional?: TOptional; + + /** + * The route (typically in another plugin) that this should map to by default. + * + * The string is expected to be on the standard `.` form, + * for example `techdocs.docRoot`. + */ + defaultTarget?: string; }): ExternalRouteRef< keyof TParams extends never ? undefined @@ -126,6 +140,7 @@ export function createExternalRouteRef< return new ExternalRouteRefImpl( Boolean(options?.optional), options?.params as string[] | undefined, + options?.defaultTarget, describeParentCallSite(), ) as ExternalRouteRef; } From 7360c3dc0a65821138c5b2ec2265ab82fd7af42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 29 Jan 2024 10:35:28 +0100 Subject: [PATCH 077/114] review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/core-compat-api/src/convertLegacyRouteRef.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.ts b/packages/core-compat-api/src/convertLegacyRouteRef.ts index e6a52932ce..a5375e0692 100644 --- a/packages/core-compat-api/src/convertLegacyRouteRef.ts +++ b/packages/core-compat-api/src/convertLegacyRouteRef.ts @@ -200,7 +200,8 @@ export function convertLegacyRouteRef( return legacyRefStr; }, getDefaultTarget() { - return newRef.getDefaultTarget(); + // TODO(freben): These are not yet supported in the old system; just returning undefined for now + return undefined; }, setId(id: string) { newRef.setId(id); From 75dd39d084edba8b23fc438b4899f7068fded8ff Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 19:08:56 +0100 Subject: [PATCH 078/114] chore: support passing through token in `fetchContents` and `fetchFile` Signed-off-by: blam Signed-off-by: blam --- .../scaffolder-node/src/actions/fetch.test.ts | 28 +++++++++++++++++++ plugins/scaffolder-node/src/actions/fetch.ts | 24 +++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-node/src/actions/fetch.test.ts b/plugins/scaffolder-node/src/actions/fetch.test.ts index 55b949fca7..42f75d02b4 100644 --- a/plugins/scaffolder-node/src/actions/fetch.test.ts +++ b/plugins/scaffolder-node/src/actions/fetch.test.ts @@ -124,6 +124,20 @@ describe('fetchContents helper', () => { expect(fs.ensureDir).toHaveBeenCalledWith('foo'); expect(dirFunction).toHaveBeenCalledWith({ targetDir: 'foo' }); }); + + it('should pass through the token provided through to the URL reader', async () => { + await fetchContents({ + ...options, + outputPath: 'mydir/foo', + fetchUrl: 'https://github.com/backstage/foo', + token: 'mockToken', + }); + + expect(readTree).toHaveBeenCalledWith( + 'https://github.com/backstage/foo', + expect.objectContaining({ token: 'mockToken' }), + ); + }); }); describe('fetch file', () => { @@ -211,5 +225,19 @@ describe('fetchContents helper', () => { expect(fs.ensureDir).toHaveBeenCalledWith('mydir'); expect(fs.outputFile).toHaveBeenCalledWith('mydir/foo', 'test'); }); + + it('should pass through the token provided through to the URL reader', async () => { + await fetchFile({ + ...options, + outputPath: 'mydir/foo', + fetchUrl: 'https://github.com/backstage/foo', + token: 'mockToken', + }); + + expect(readUrl).toHaveBeenCalledWith( + 'https://github.com/backstage/foo', + expect.objectContaining({ token: 'mockToken' }), + ); + }); }); }); diff --git a/plugins/scaffolder-node/src/actions/fetch.ts b/plugins/scaffolder-node/src/actions/fetch.ts index 66bbc0a842..2ddc2b1ae6 100644 --- a/plugins/scaffolder-node/src/actions/fetch.ts +++ b/plugins/scaffolder-node/src/actions/fetch.ts @@ -32,8 +32,16 @@ export async function fetchContents(options: { baseUrl?: string; fetchUrl?: string; outputPath: string; + token?: string; }) { - const { reader, integrations, baseUrl, fetchUrl = '.', outputPath } = options; + const { + reader, + integrations, + baseUrl, + fetchUrl = '.', + outputPath, + token, + } = options; const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl); @@ -45,7 +53,7 @@ export async function fetchContents(options: { } else { const readUrl = getReadUrl(fetchUrl, baseUrl, integrations); - const res = await reader.readTree(readUrl); + const res = await reader.readTree(readUrl, { token }); await fs.ensureDir(outputPath); await res.dir({ targetDir: outputPath }); } @@ -63,8 +71,16 @@ export async function fetchFile(options: { baseUrl?: string; fetchUrl?: string; outputPath: string; + token?: string; }) { - const { reader, integrations, baseUrl, fetchUrl = '.', outputPath } = options; + const { + reader, + integrations, + baseUrl, + fetchUrl = '.', + outputPath, + token, + } = options; const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl); @@ -76,7 +92,7 @@ export async function fetchFile(options: { } else { const readUrl = getReadUrl(fetchUrl, baseUrl, integrations); - const res = await reader.readUrl(readUrl); + const res = await reader.readUrl(readUrl, { token }); await fs.ensureDir(path.dirname(outputPath)); const buffer = await res.buffer(); await fs.outputFile(outputPath, buffer.toString()); From ed0f7336d86e52879d72355fff45b14ee501ca8a Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 19:21:58 +0100 Subject: [PATCH 079/114] feat: pass through the the token in scaffolder Signed-off-by: blam --- .../actions/builtin/fetch/plain.test.ts | 19 +++++++++++++++++++ .../scaffolder/actions/builtin/fetch/plain.ts | 13 ++++++++++++- .../actions/builtin/fetch/plainFile.test.ts | 15 +++++++++++++++ .../actions/builtin/fetch/plainFile.ts | 13 ++++++++++++- .../actions/builtin/fetch/template.test.ts | 12 ++++++++++++ .../actions/builtin/fetch/template.ts | 8 ++++++++ 6 files changed, 78 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index 249941096d..ad179d8269 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -85,4 +85,23 @@ describe('fetch:plain', () => { }), ); }); + + it('should fetch plain with token', async () => { + await action.handler({ + ...mockContext, + input: { + url: 'https://github.com/backstage/community/tree/main/backstage-community-sessions/assets', + targetPath: 'lol', + token: 'mockToken', + }, + }); + + expect(fetchContents).toHaveBeenCalledWith( + expect.objectContaining({ + outputPath: resolvePath(mockContext.workspacePath, 'lol'), + fetchUrl: + 'https://github.com/backstage/community/tree/main/backstage-community-sessions/assets', + }), + ); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index 57f3d1121b..8bca630af9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -36,7 +36,11 @@ export function createFetchPlainAction(options: { }) { const { reader, integrations } = options; - return createTemplateAction<{ url: string; targetPath?: string }>({ + return createTemplateAction<{ + url: string; + targetPath?: string; + token?: string; + }>({ id: ACTION_ID, examples, description: @@ -58,6 +62,12 @@ export function createFetchPlainAction(options: { 'Target path within the working directory to download the contents to.', type: 'string', }, + token: { + title: 'Token', + description: + 'An optional token to use for authentication when reading the resources.', + type: 'string', + }, }, }, }, @@ -75,6 +85,7 @@ export function createFetchPlainAction(options: { baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath, + token: ctx.input.token, }); }, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts index 084b08d640..7ea74a809b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts @@ -69,6 +69,21 @@ describe('fetch:plain:file', () => { ); }); + it('passed through the token to fetchFile', async () => { + await action.handler({ + ...mockContext, + input: { + url: 'https://github.com/backstage/community/tree/main/backstage-community-sessions/assets/Backstage%20Community%20Sessions.png', + token: 'mockToken', + targetPath: 'lol', + }, + }); + + expect(fetchFile).toHaveBeenCalledWith( + expect.objectContaining({ token: 'mockToken' }), + ); + }); + it('should fetch plain', async () => { await action.handler({ ...mockContext, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts index 0a791a31a4..da0255ff5a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts @@ -33,7 +33,11 @@ export function createFetchPlainFileAction(options: { }) { const { reader, integrations } = options; - return createTemplateAction<{ url: string; targetPath: string }>({ + return createTemplateAction<{ + url: string; + targetPath: string; + token?: string; + }>({ id: 'fetch:plain:file', description: 'Downloads single file and places it in the workspace.', examples, @@ -54,6 +58,12 @@ export function createFetchPlainFileAction(options: { 'Target path within the working directory to download the file as.', type: 'string', }, + token: { + title: 'Token', + description: + 'An optional token to use for authentication when reading the resources.', + type: 'string', + }, }, }, }, @@ -73,6 +83,7 @@ export function createFetchPlainFileAction(options: { baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath, + token: ctx.input.token, }); }, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 119cd78eb7..25b65462e6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -371,6 +371,18 @@ describe('fetch:template', () => { fs.readlink(`${workspacePath}/target/brokenSymlink`), ).resolves.toEqual(`.${pathSep}not-a-real-file.txt`); }); + + it('passed through the token to the fetchContents call', async () => { + await action.handler( + mockContext({ + token: 'mockToken', + }), + ); + + expect(mockFetchContents).toHaveBeenCalledWith( + expect.objectContaining({ token: 'mockToken' }), + ); + }); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 2a2c138708..63a595ef80 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -71,6 +71,7 @@ export function createFetchTemplateAction(options: { replace?: boolean; trimBlocks?: boolean; lstripBlocks?: boolean; + token?: string; }>({ id: 'fetch:template', description: @@ -134,6 +135,12 @@ export function createFetchTemplateAction(options: { 'If set, replace files in targetPath instead of skipping existing ones.', type: 'boolean', }, + token: { + title: 'Token', + description: + 'An optional token to use for authentication when reading the resources.', + type: 'string', + }, }, }, }, @@ -197,6 +204,7 @@ export function createFetchTemplateAction(options: { baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath: templateDir, + token: ctx.input.token, }); ctx.logger.info('Listing files and directories in template'); From 78c100bc60c21f4646795e53e0c817e2bb6dcfa3 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 19:53:13 +0100 Subject: [PATCH 080/114] chore: add changeset Signed-off-by: blam Signed-off-by: blam --- .changeset/ten-trainers-cough.md | 6 ++++++ .../src/scaffolder/actions/builtin/fetch/plain.test.ts | 1 + 2 files changed, 7 insertions(+) create mode 100644 .changeset/ten-trainers-cough.md diff --git a/.changeset/ten-trainers-cough.md b/.changeset/ten-trainers-cough.md new file mode 100644 index 0000000000..53a82def90 --- /dev/null +++ b/.changeset/ten-trainers-cough.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-node': minor +--- + +Support providing an overriding token for `fetch:template`, `fetch:plain` and `fetch:file` when interacting with upstream integrations diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index ad179d8269..917624acf4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -77,6 +77,7 @@ describe('fetch:plain', () => { targetPath: 'lol', }, }); + expect(fetchContents).toHaveBeenCalledWith( expect.objectContaining({ outputPath: resolvePath(mockContext.workspacePath, 'lol'), From e9987a467dbec75c06e077541356a7d6422b5875 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 20:14:40 +0100 Subject: [PATCH 081/114] chore: support overriding the token Signed-off-by: blam --- .../src/reading/GithubUrlReader.test.ts | 65 +++++++++++++++++++ .../src/reading/GithubUrlReader.ts | 28 ++++++-- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index 5e6d30f519..ec775183b3 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -271,6 +271,33 @@ describe('GithubUrlReader', () => { expect(response.etag).toBe('foo'); expect(response.lastModifiedAt).toEqual(new Date('2021-01-01T00:00:00Z')); }); + + it('should override the token if its provided', async () => { + expect.assertions(1); + + (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + headers: { + Authorization: 'bearer blah', + }, + }); + + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + return res(ctx.status(200)); + }, + ), + ); + + await gheProcessor.readUrl( + 'https://github.com/backstage/mock/tree/blob/main', + { token: 'overridentoken' }, + ); + }); }); /* @@ -463,6 +490,44 @@ describe('GithubUrlReader', () => { ); }); + it('should override the token when provided', async () => { + expect.assertions(1); + + const mockHeaders = { + Authorization: 'bearer blah', + }; + + (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + headers: mockHeaders, + }); + + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/x-gzip'), + ctx.set( + 'content-disposition', + 'attachment; filename=backstage-mock-etag123.tar.gz', + ), + ctx.body(repoBuffer), + ); + }, + ), + ); + + await gheProcessor.readTree( + 'https://ghe.github.com/backstage/mock/tree/main', + { token: 'overridentoken' }, + ); + }); + it('includes the subdomain in the github url', async () => { const response = await gheProcessor.readTree( 'https://ghe.github.com/backstage/mock/tree/main/docs', diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 462821a20f..b476435771 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -92,13 +92,31 @@ export class GithubUrlReader implements UrlReader { return response.buffer(); } + private getCredentials = async ( + url: string, + options?: { token?: string }, + ): Promise => { + if (options?.token) { + return { + headers: { + Authorization: `Bearer ${options.token}`, + }, + type: 'token', + token: options.token, + }; + } + + return await this.deps.credentialsProvider.getCredentials({ + url, + }); + }; + async readUrl( url: string, options?: ReadUrlOptions, ): Promise { - const credentials = await this.deps.credentialsProvider.getCredentials({ - url, - }); + const credentials = await this.getCredentials(url, options); + const ghUrl = getGithubFileFetchUrl( url, this.integration.config, @@ -141,9 +159,7 @@ export class GithubUrlReader implements UrlReader { } const { filepath } = parseGitUrl(url); - const { headers } = await this.deps.credentialsProvider.getCredentials({ - url, - }); + const { headers } = await this.getCredentials(url, options); return this.doReadTree( repoDetails.repo.archive_url, From 1e1dd798fa2bd2605c07729db35922ddcd22e2f2 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 20:15:06 +0100 Subject: [PATCH 082/114] chore: updating reading tyupes Signed-off-by: blam --- .../services/definitions/UrlReaderService.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index c189b4a339..e89deab0eb 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -92,6 +92,18 @@ export type ReadUrlOptions = { * Not all reader implementations may take this field into account. */ signal?: AbortSignal; + + /** + * An optional token to use for authentication when reading the resources. + * + * @remarks + * + * By default all URL Readers will use the integrations config which is supplied + * when creating the Readers. Sometimes it might be desireable to use the already + * created URLReaders but with a different token, maybe that's supplied by the user + * at runtime. + */ + token?: string; }; /** @@ -179,6 +191,18 @@ export type ReadTreeOptions = { * Not all reader implementations may take this field into account. */ signal?: AbortSignal; + + /** + * An optional token to use for authentication when reading the resources. + * + * @remarks + * + * By default all URL Readers will use the integrations config which is supplied + * when creating the Readers. Sometimes it might be desireable to use the already + * created URLReaders but with a different token, maybe that's supplied by the user + * at runtime. + */ + token?: string; }; /** From 98753d9bf0f3636e3e17538396ad2edc74c27f40 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 20:20:58 +0100 Subject: [PATCH 083/114] chore: update api-reports Signed-off-by: blam --- packages/backend-plugin-api/api-report.md | 2 ++ plugins/scaffolder-backend/api-report.md | 3 +++ plugins/scaffolder-node/api-report.md | 2 ++ 3 files changed, 7 insertions(+) diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 7807dd96c6..e4d11c7b62 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -316,6 +316,7 @@ export type ReadTreeOptions = { ): boolean; etag?: string; signal?: AbortSignal; + token?: string; }; // @public @@ -343,6 +344,7 @@ export type ReadUrlOptions = { etag?: string; lastModifiedAfter?: Date; signal?: AbortSignal; + token?: string; }; // @public diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 92e0d86167..074bc92bf5 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -145,6 +145,7 @@ export function createFetchPlainAction(options: { { url: string; targetPath?: string | undefined; + token?: string | undefined; }, JsonObject >; @@ -157,6 +158,7 @@ export function createFetchPlainFileAction(options: { { url: string; targetPath: string; + token?: string | undefined; }, JsonObject >; @@ -179,6 +181,7 @@ export function createFetchTemplateAction(options: { replace?: boolean | undefined; trimBlocks?: boolean | undefined; lstripBlocks?: boolean | undefined; + token?: string | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 6b1e6a54b6..fec2205462 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -195,6 +195,7 @@ export function fetchContents(options: { baseUrl?: string; fetchUrl?: string; outputPath: string; + token?: string; }): Promise; // @public @@ -204,6 +205,7 @@ export function fetchFile(options: { baseUrl?: string; fetchUrl?: string; outputPath: string; + token?: string; }): Promise; // @public (undocumented) From 1f020fe05c9e07d77f34bdd662fc50e2628df996 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 25 Jan 2024 20:24:47 +0100 Subject: [PATCH 084/114] chore: add chantgeset Signed-off-by: blam --- .changeset/quick-ties-stare.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/quick-ties-stare.md diff --git a/.changeset/quick-ties-stare.md b/.changeset/quick-ties-stare.md new file mode 100644 index 0000000000..137344512b --- /dev/null +++ b/.changeset/quick-ties-stare.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': patch +'@backstage/backend-common': patch +--- + +Support `token` in `readTree` and `readUrl` From f937aae0c241653d6ed7e824784c926b5b35084a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20V=C4=82LCIU?= Date: Mon, 29 Jan 2024 12:59:38 +0200 Subject: [PATCH 085/114] catalog-graph: reduce the number of API requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use `CatalogClient.getEntitiesByRefs()` to reduce the number of requests to the backend; Signed-off-by: Valentin VĂLCIU --- .changeset/fair-mirrors-hammer.md | 5 ++ .../CatalogGraphCard.test.tsx | 55 +++++++++++++---- .../CatalogGraphPage.test.tsx | 44 +++++++++---- .../useEntityStore.test.ts | 61 ++++++++++++++++--- .../EntityRelationsGraph/useEntityStore.ts | 53 ++++++---------- 5 files changed, 147 insertions(+), 71 deletions(-) create mode 100644 .changeset/fair-mirrors-hammer.md diff --git a/.changeset/fair-mirrors-hammer.md b/.changeset/fair-mirrors-hammer.md new file mode 100644 index 0000000000..3884835d11 --- /dev/null +++ b/.changeset/fair-mirrors-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +use `CatalogClient.getEntitiesByRefs()` to reduce the number of backend requests from plugin `catalog-graph` diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index cdd55e300f..1f6c77ccf3 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -40,6 +40,7 @@ describe('', () => { const catalog = { getEntities: jest.fn(), getEntityByRef: jest.fn(), + getEntitiesByRefs: jest.fn(), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), getLocationByRef: jest.fn(), @@ -75,9 +76,13 @@ describe('', () => { }); test('renders without exploding', async () => { - catalog.getEntityByRef.mockImplementation(async _ => ({ - ...entity, - relations: [], + catalog.getEntitiesByRefs.mockImplementation(async _ => ({ + items: [ + { + ...entity, + relations: [], + }, + ], })); await renderInTestApp(wrapper, { @@ -89,13 +94,20 @@ describe('', () => { expect(await screen.findByText('b:d/c')).toBeInTheDocument(); expect(await screen.findAllByTestId('node')).toHaveLength(1); - expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1); + expect(catalog.getEntitiesByRefs).toHaveBeenCalledTimes(1); + expect(catalog.getEntitiesByRefs).toHaveBeenCalledWith( + expect.objectContaining({ entityRefs: ['b:d/c'] }), + ); }); test('renders with custom title', async () => { - catalog.getEntityByRef.mockImplementation(async _ => ({ - ...entity, - relations: [], + catalog.getEntitiesByRefs.mockImplementation(async _ => ({ + items: [ + { + ...entity, + relations: [], + }, + ], })); await renderInTestApp( @@ -116,9 +128,13 @@ describe('', () => { }); test('renders link to standalone viewer', async () => { - catalog.getEntityByRef.mockImplementation(async _ => ({ - ...entity, - relations: [], + catalog.getEntitiesByRefs.mockImplementation(async _ => ({ + items: [ + { + ...entity, + relations: [], + }, + ], })); await renderInTestApp(wrapper, { @@ -138,6 +154,15 @@ describe('', () => { }); test('renders link to standalone viewer with custom config', async () => { + catalog.getEntitiesByRefs.mockImplementation(async _ => ({ + items: [ + { + ...entity, + relations: [], + }, + ], + })); + await renderInTestApp( @@ -162,9 +187,13 @@ describe('', () => { }); test('captures analytics event on click', async () => { - catalog.getEntityByRef.mockImplementation(async _ => ({ - ...entity, - relations: [], + catalog.getEntitiesByRefs.mockImplementation(async _ => ({ + items: [ + { + ...entity, + relations: [], + }, + ], })); const analyticsSpy = new MockAnalyticsApi(); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx index f2360b678d..e188dcc7ff 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx @@ -26,6 +26,7 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { CatalogGraphPage } from './CatalogGraphPage'; +import { GetEntitiesByRefsRequest } from '@backstage/catalog-client'; const navigate = jest.fn(); @@ -111,9 +112,14 @@ describe.skip('', () => { }, ], }; + const allEntities: Record = { + 'b:d/c': entityC, + 'b:d/e': entityE, + }; const catalog = { getEntities: jest.fn(), getEntityByRef: jest.fn(), + getEntitiesByRefs: jest.fn(), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), getLocationByRef: jest.fn(), @@ -142,8 +148,10 @@ describe.skip('', () => { afterEach(() => jest.resetAllMocks()); test('should render without exploding', async () => { - catalog.getEntityByRef.mockImplementation(async (n: any) => - n === 'b:d/e' ? entityE : entityC, + catalog.getEntitiesByRefs.mockImplementation( + async ({ entityRefs }: GetEntitiesByRefsRequest) => ({ + items: entityRefs.map(ref => allEntities[ref]), + }), ); await renderInTestApp(wrapper, { @@ -156,12 +164,14 @@ describe.skip('', () => { await expect(screen.findByText('b:d/c')).resolves.toBeInTheDocument(); await expect(screen.findByText('b:d/e')).resolves.toBeInTheDocument(); await expect(screen.findAllByTestId('node')).resolves.toHaveLength(2); - expect(catalog.getEntityByRef).toHaveBeenCalledTimes(2); + expect(catalog.getEntitiesByRefs).toHaveBeenCalledTimes(2); }); test('should toggle filters', async () => { - catalog.getEntityByRef.mockImplementation(async (n: any) => - n === 'b:d/e' ? entityE : entityC, + catalog.getEntitiesByRefs.mockImplementation( + async ({ entityRefs }: GetEntitiesByRefsRequest) => ({ + items: entityRefs.map(ref => allEntities[ref]), + }), ); await renderInTestApp(wrapper, { @@ -178,8 +188,10 @@ describe.skip('', () => { }); test('should select other entity', async () => { - catalog.getEntityByRef.mockImplementation(async (n: any) => - n === 'b:d/e' ? entityE : entityC, + catalog.getEntitiesByRefs.mockImplementation( + async ({ entityRefs }: GetEntitiesByRefsRequest) => ({ + items: entityRefs.map(ref => allEntities[ref]), + }), ); await renderInTestApp(wrapper, { @@ -196,8 +208,10 @@ describe.skip('', () => { }); test('should navigate to entity', async () => { - catalog.getEntityByRef.mockImplementation(async (n: any) => - n === 'b:d/e' ? entityE : entityC, + catalog.getEntitiesByRefs.mockImplementation( + async ({ entityRefs }: GetEntitiesByRefsRequest) => ({ + items: entityRefs.map(ref => allEntities[ref]), + }), ); await renderInTestApp(wrapper, { @@ -215,8 +229,10 @@ describe.skip('', () => { }); test('should capture analytics event when selecting other entity', async () => { - catalog.getEntityByRef.mockImplementation(async (n: any) => - n === 'b:d/e' ? entityE : entityC, + catalog.getEntitiesByRefs.mockImplementation( + async ({ entityRefs }: GetEntitiesByRefsRequest) => ({ + items: entityRefs.map(ref => allEntities[ref]), + }), ); const analyticsSpy = new MockAnalyticsApi(); @@ -242,8 +258,10 @@ describe.skip('', () => { }); test('should capture analytics event when navigating to entity', async () => { - catalog.getEntityByRef.mockImplementation(async (n: any) => - n === 'b:d/e' ? entityE : entityC, + catalog.getEntitiesByRefs.mockImplementation( + async ({ entityRefs }: GetEntitiesByRefsRequest) => ({ + items: entityRefs.map(ref => allEntities[ref]), + }), ); const analyticsSpy = new MockAnalyticsApi(); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts index 0d75fabc3b..786c37ca7e 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts @@ -27,6 +27,7 @@ describe('useEntityStore', () => { const catalogApi = { getEntities: jest.fn(), getEntityByRef: jest.fn(), + getEntitiesByRefs: jest.fn(), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), getLocationByRef: jest.fn(), @@ -63,7 +64,7 @@ describe('useEntityStore', () => { }, }; - catalogApi.getEntityByRef.mockResolvedValue(entity); + catalogApi.getEntitiesByRefs.mockResolvedValue({ items: [entity] }); const { result } = renderHook(() => useEntityStore()); @@ -83,7 +84,7 @@ describe('useEntityStore', () => { test('handles request failures', async () => { const err = new Error('Hello World'); - catalogApi.getEntityByRef.mockRejectedValue(err); + catalogApi.getEntitiesByRefs.mockRejectedValue(err); const { result } = renderHook(() => useEntityStore()); @@ -100,7 +101,7 @@ describe('useEntityStore', () => { }); test('handles loading', async () => { - catalogApi.getEntityByRef.mockReturnValue(new Promise(() => {})); + catalogApi.getEntitiesByRefs.mockReturnValue(new Promise(() => {})); const { result } = renderHook(() => useEntityStore()); @@ -131,8 +132,16 @@ describe('useEntityStore', () => { name: 'name2', }, }; + const entity3: Entity = { + apiVersion: 'v1', + kind: 'kind', + metadata: { + namespace: 'namespace', + name: 'name3', + }, + }; - catalogApi.getEntityByRef.mockResolvedValue(entity1); + catalogApi.getEntitiesByRefs.mockResolvedValue({ items: [entity1] }); const { result } = renderHook(() => useEntityStore()); @@ -149,12 +158,15 @@ describe('useEntityStore', () => { }); }); - catalogApi.getEntityByRef.mockResolvedValue(entity2); + catalogApi.getEntitiesByRefs.mockResolvedValue({ + items: [entity2, entity3], + }); act(() => { result.current.requestEntities([ 'kind:namespace/name1', 'kind:namespace/name2', + 'kind:namespace/name3', ]); }); @@ -165,6 +177,7 @@ describe('useEntityStore', () => { expect(entities).toEqual({ 'kind:namespace/name1': entity1, 'kind:namespace/name2': entity2, + 'kind:namespace/name3': entity3, }); }); }); @@ -186,13 +199,26 @@ describe('useEntityStore', () => { name: 'name2', }, }; + const entity3: Entity = { + apiVersion: 'v1', + kind: 'kind', + metadata: { + namespace: 'namespace', + name: 'name3', + }, + }; - catalogApi.getEntityByRef.mockResolvedValue(entity1); + catalogApi.getEntitiesByRefs.mockResolvedValue({ + items: [entity1, entity2], + }); const { result } = renderHook(() => useEntityStore()); act(() => { - result.current.requestEntities(['kind:namespace/name1']); + result.current.requestEntities([ + 'kind:namespace/name1', + 'kind:namespace/name2', + ]); }); await waitFor(() => { @@ -201,13 +227,22 @@ describe('useEntityStore', () => { expect(error).toBeUndefined(); expect(entities).toEqual({ 'kind:namespace/name1': entity1, + 'kind:namespace/name2': entity2, }); }); - catalogApi.getEntityByRef.mockResolvedValue(entity2); + expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(1); + expect(catalogApi.getEntitiesByRefs).toHaveBeenLastCalledWith({ + entityRefs: ['kind:namespace/name1', 'kind:namespace/name2'], + }); + + catalogApi.getEntitiesByRefs.mockResolvedValue({ items: [entity3] }); act(() => { - result.current.requestEntities(['kind:namespace/name2']); + result.current.requestEntities([ + 'kind:namespace/name2', + 'kind:namespace/name3', + ]); }); await waitFor(() => { @@ -216,9 +251,15 @@ describe('useEntityStore', () => { expect(error).toBeUndefined(); expect(entities).toEqual({ 'kind:namespace/name2': entity2, + 'kind:namespace/name3': entity3, }); }); + expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(2); + expect(catalogApi.getEntitiesByRefs).toHaveBeenLastCalledWith({ + entityRefs: ['kind:namespace/name3'], + }); + act(() => { result.current.requestEntities(['kind:namespace/name1']); }); @@ -232,6 +273,6 @@ describe('useEntityStore', () => { }); }); - expect(catalogApi.getEntityByRef).toHaveBeenCalledTimes(2); + expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(2); }); }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.ts index 843b308281..5c33f8053d 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.ts @@ -13,18 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import limiterFactory from 'p-limit'; import { Dispatch, useCallback, useRef, useState } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; // TODO: This is a good use case for a graphql API, once it is available in the // future. -const limiter = limiterFactory(10); - /** * Ensures that a set of requested entities is loaded. */ @@ -58,38 +55,24 @@ export function useEntityStore(): { }, [state, setEntities]); const [asyncState, fetch] = useAsyncFn(async () => { - const { requestedEntities, outstandingEntities, cachedEntities } = - state.current; - - await Promise.all( - Array.from(requestedEntities).map(entityRef => - limiter(async () => { - if (cachedEntities.has(entityRef)) { - return; - } - - if (outstandingEntities.has(entityRef)) { - await outstandingEntities.get(entityRef); - return; - } - - const promise = catalogClient.getEntityByRef(entityRef); - - outstandingEntities.set(entityRef, promise); - - try { - const entity = await promise; - - if (entity) { - cachedEntities.set(entityRef, entity); - updateEntities(); - } - } finally { - outstandingEntities.delete(entityRef); - } - }), - ), + const { requestedEntities, cachedEntities } = state.current; + const entityRefs: string[] = Array.from(requestedEntities).filter( + entityRef => !cachedEntities.has(entityRef), ); + if (entityRefs.length === 0) { + updateEntities(); + return; + } + + const { items } = await catalogClient.getEntitiesByRefs({ entityRefs }); + items.forEach(ent => { + if (ent) { + const entityRef = stringifyEntityRef(ent); + cachedEntities.set(entityRef, ent); + } + }); + + updateEntities(); }, [state, updateEntities]); const { loading, error } = asyncState; From f640129d84c6de88fa4a499ffa0258f2ae51e0e9 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jan 2024 13:29:44 +0100 Subject: [PATCH 086/114] chore: tidying up tests and implementing for `search` also Signed-off-by: blam --- .../src/reading/GithubUrlReader.test.ts | 44 +++++++++++++++---- .../src/reading/GithubUrlReader.ts | 4 +- .../services/definitions/UrlReaderService.ts | 12 +++++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index ec775183b3..f07ada0cc3 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -46,7 +46,7 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({ const mockCredentialsProvider = { getCredentials: jest.fn().mockResolvedValue({ headers: {} }), -} as unknown as GithubCredentialsProvider; +} satisfies GithubCredentialsProvider; const githubProcessor = new GithubUrlReader( new GithubIntegration( @@ -105,7 +105,7 @@ describe('GithubUrlReader', () => { otherheader: 'something', }; - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: mockHeaders, }); @@ -146,7 +146,7 @@ describe('GithubUrlReader', () => { otherheader: 'something', }; - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: mockHeaders, }); @@ -182,7 +182,7 @@ describe('GithubUrlReader', () => { otherheader: 'something', }; - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: mockHeaders, }); @@ -241,7 +241,7 @@ describe('GithubUrlReader', () => { }); it('should return etag and last-modified from the response', async () => { - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: { Authorization: 'bearer blah', }, @@ -275,7 +275,7 @@ describe('GithubUrlReader', () => { it('should override the token if its provided', async () => { expect.assertions(1); - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: { Authorization: 'bearer blah', }, @@ -458,7 +458,7 @@ describe('GithubUrlReader', () => { otherheader: 'something', }; - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: mockHeaders, }); @@ -497,7 +497,7 @@ describe('GithubUrlReader', () => { Authorization: 'bearer blah', }; - (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + mockCredentialsProvider.getCredentials.mockResolvedValue({ headers: mockHeaders, }); @@ -970,6 +970,34 @@ describe('GithubUrlReader', () => { await runTests(gheProcessor, 'https://ghe.github.com'); }); + it('passes through a token for the search request', async () => { + expect.assertions(1); + + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + truncated: true, + tree: [], + } as Partial), + ); + }, + ), + ); + + await gheProcessor.search( + `https://ghe.github.com/backstage/mock/tree/main/**/*`, + { token: 'overridentoken' }, + ); + }); + // eslint-disable-next-line jest/expect-expect it('succeeds on ghe when going via readTree', async () => { worker.use( diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index b476435771..b49945013d 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -185,9 +185,7 @@ export class GithubUrlReader implements UrlReader { } const { filepath } = parseGitUrl(url); - const { headers } = await this.deps.credentialsProvider.getCredentials({ - url, - }); + const { headers } = await this.getCredentials(url, options); const files = await this.doSearch( url, diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index e89deab0eb..734dcd9902 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -305,6 +305,18 @@ export type SearchOptions = { * Not all reader implementations may take this field into account. */ signal?: AbortSignal; + + /** + * An optional token to use for authentication when reading the resources. + * + * @remarks + * + * By default all URL Readers will use the integrations config which is supplied + * when creating the Readers. Sometimes it might be desireable to use the already + * created URLReaders but with a different token, maybe that's supplied by the user + * at runtime. + */ + token?: string; }; /** From a8b0c802b0904f1c5b0dc6785829aab91f81ae03 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jan 2024 14:12:36 +0100 Subject: [PATCH 087/114] chore: make sure to keep the existing uiSchema when parsing dependencies Signed-off-by: blam --- .../src/next/lib/schema.test.ts | 107 ++++++++++++++++++ .../scaffolder-react/src/next/lib/schema.ts | 10 +- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-react/src/next/lib/schema.test.ts b/plugins/scaffolder-react/src/next/lib/schema.test.ts index 4dc6cda603..5bfd65133c 100644 --- a/plugins/scaffolder-react/src/next/lib/schema.test.ts +++ b/plugins/scaffolder-react/src/next/lib/schema.test.ts @@ -412,6 +412,113 @@ describe('extractSchemaFromStep', () => { }); }); + it('doesnt override existing uiSchema with things from dependencies', () => { + const inputSchema: JsonObject = { + type: 'object', + title: + "Field 2 depend on field 1, field 1 is a radio button but it's visible as a field", + required: ['exampleField1'], + properties: { + exampleField0: { + title: 'Radio button that is not a dependency', + type: 'string', + enum: ['foo', 'bar'], + 'ui:widget': 'radio', + }, + exampleField1: { + title: 'Radio button input that is a dependency', + type: 'string', + enum: ['visible', 'hidden'], + 'ui:widget': 'radio', + }, + }, + dependencies: { + exampleField1: { + oneOf: [ + { + properties: { + exampleField1: { + enum: ['visible'], + }, + exampleField2: { + title: 'FIELD 2', + type: 'string', + description: 'Explanation', + }, + }, + }, + { + properties: { + exampleField1: { + enum: ['hidden'], + }, + }, + }, + ], + }, + }, + }; + + const expectedSchema = { + type: 'object', + title: + "Field 2 depend on field 1, field 1 is a radio button but it's visible as a field", + required: ['exampleField1'], + properties: { + exampleField0: { + title: 'Radio button that is not a dependency', + type: 'string', + enum: ['foo', 'bar'], + }, + exampleField1: { + title: 'Radio button input that is a dependency', + type: 'string', + enum: ['visible', 'hidden'], + }, + }, + dependencies: { + exampleField1: { + oneOf: [ + { + properties: { + exampleField1: { + enum: ['visible'], + }, + exampleField2: { + title: 'FIELD 2', + type: 'string', + description: 'Explanation', + }, + }, + }, + { + properties: { + exampleField1: { + enum: ['hidden'], + }, + }, + }, + ], + }, + }, + }; + + const expectedUiSchema = { + exampleField0: { + 'ui:widget': 'radio', + }, + exampleField1: { + 'ui:widget': 'radio', + }, + exampleField2: {}, + }; + + expect(extractSchemaFromStep(inputSchema)).toEqual({ + schema: expectedSchema, + uiSchema: expectedUiSchema, + }); + }); + it('transforms conditional schema', () => { const inputSchema: JsonObject = { type: 'object', diff --git a/plugins/scaffolder-react/src/next/lib/schema.ts b/plugins/scaffolder-react/src/next/lib/schema.ts index b7c1364f41..2ca2a6fa7b 100644 --- a/plugins/scaffolder-react/src/next/lib/schema.ts +++ b/plugins/scaffolder-react/src/next/lib/schema.ts @@ -58,9 +58,13 @@ function extractUiSchema(schema: JsonObject, uiSchema: JsonObject) { if (!isObject(schemaNode)) { continue; } - const innerUiSchema = {}; - uiSchema[propName] = innerUiSchema; - extractUiSchema(schemaNode, innerUiSchema); + + if (!isObject(uiSchema[propName])) { + const innerUiSchema = {}; + uiSchema[propName] = innerUiSchema; + } + + extractUiSchema(schemaNode, uiSchema[propName] as JsonObject); } } From 82affc70afa0f98e8a2735facccda2a7c255b92b Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jan 2024 14:13:26 +0100 Subject: [PATCH 088/114] chore: updating ui:schema Signed-off-by: blam --- .changeset/twelve-weeks-march.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/twelve-weeks-march.md diff --git a/.changeset/twelve-weeks-march.md b/.changeset/twelve-weeks-march.md new file mode 100644 index 0000000000..4d9fff5b34 --- /dev/null +++ b/.changeset/twelve-weeks-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Fix issue where `ui:schema` was replaced with an empty object if `dependencies` is defined From dca3b7251a942e1f1e2cbc2364a71a66bc950b69 Mon Sep 17 00:00:00 2001 From: Jean-Louis FEREY Date: Mon, 29 Jan 2024 14:14:35 +0100 Subject: [PATCH 089/114] Update v1.22.0.md A little correction: Gitlab instead of Github for the new Gitlab scaffolder action Signed-off-by: Jean-Louis FEREY --- docs/releases/v1.22.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/v1.22.0.md b/docs/releases/v1.22.0.md index e5ce345ccc..0c2d3b4d5f 100644 --- a/docs/releases/v1.22.0.md +++ b/docs/releases/v1.22.0.md @@ -39,7 +39,7 @@ Contributed by [@davidfestal](https://github.com/davidfestal) in [#18862](https: ### New Scaffolder action `gitlab:issues:create` -You can now create GitHub issues in your scaffolder flows! Contributed by [@elaine-mattos](https://github.com/elaine-mattos) in [#21929](https://github.com/backstage/backstage/pull/21929) +You can now create Gitlab issues in your scaffolder flows! Contributed by [@elaine-mattos](https://github.com/elaine-mattos) in [#21929](https://github.com/backstage/backstage/pull/21929) ### New Scaffolder action `gitlab:repo:push` From fb0ae81b6a93a8abec72e200de619747e0334dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 29 Jan 2024 14:18:16 +0100 Subject: [PATCH 090/114] Update docs/releases/v1.22.0.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/releases/v1.22.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/v1.22.0.md b/docs/releases/v1.22.0.md index 0c2d3b4d5f..9f9295fb96 100644 --- a/docs/releases/v1.22.0.md +++ b/docs/releases/v1.22.0.md @@ -39,7 +39,7 @@ Contributed by [@davidfestal](https://github.com/davidfestal) in [#18862](https: ### New Scaffolder action `gitlab:issues:create` -You can now create Gitlab issues in your scaffolder flows! Contributed by [@elaine-mattos](https://github.com/elaine-mattos) in [#21929](https://github.com/backstage/backstage/pull/21929) +You can now create GitLab issues in your scaffolder flows! Contributed by [@elaine-mattos](https://github.com/elaine-mattos) in [#21929](https://github.com/backstage/backstage/pull/21929) ### New Scaffolder action `gitlab:repo:push` From 54353b124f18186fd3ad197c10bd2f8ec15b2762 Mon Sep 17 00:00:00 2001 From: Luis Komatsu Date: Fri, 5 Jan 2024 16:07:05 -0300 Subject: [PATCH 091/114] feat: markdown code no word-break Signed-off-by: Luis Komatsu --- .../techdocs/src/reader/transformers/styles/rules/typeset.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts index 4c1eef1ea4..cc7ba6bace 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts @@ -115,4 +115,8 @@ ${headings.reduce((style, heading) => { .md-typeset pre > code::-webkit-scrollbar-thumb:hover { background-color: hsla(0, 0%, 0%, 0.87); } + +.md-typeset code { + word-break: keep-all; +} `; From af4d147ef7a7395a8d9b3e965e36e5e4b80fe738 Mon Sep 17 00:00:00 2001 From: Luis Komatsu Date: Tue, 9 Jan 2024 14:42:42 -0300 Subject: [PATCH 092/114] fix: changeset Signed-off-by: Luis Komatsu --- .changeset/tender-flowers-collect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-flowers-collect.md diff --git a/.changeset/tender-flowers-collect.md b/.changeset/tender-flowers-collect.md new file mode 100644 index 0000000000..ea26f3b69a --- /dev/null +++ b/.changeset/tender-flowers-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +code tag css style changed to avoid word break From f643e910f98af60327b8714a57e078352d338316 Mon Sep 17 00:00:00 2001 From: Luis Komatsu Date: Mon, 29 Jan 2024 17:01:19 -0300 Subject: [PATCH 093/114] fix: changeset description Signed-off-by: Luis Komatsu --- .changeset/tender-flowers-collect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tender-flowers-collect.md b/.changeset/tender-flowers-collect.md index ea26f3b69a..dfbf96a52c 100644 --- a/.changeset/tender-flowers-collect.md +++ b/.changeset/tender-flowers-collect.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs': minor --- -code tag css style changed to avoid word break +Updated the styling for tags to avoid word break. From da98911e7d04d1219c5b8df9304dbf6c20dcc89c Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 Jan 2024 09:15:50 +0100 Subject: [PATCH 094/114] chore: updating api-reports Signed-off-by: blam --- packages/backend-plugin-api/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index e4d11c7b62..3b3d28cc27 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -392,6 +392,7 @@ export interface SchedulerService extends PluginTaskScheduler {} export type SearchOptions = { etag?: string; signal?: AbortSignal; + token?: string; }; // @public From 292c37b2fceac27131fd153bba3dde5f8fe09d3b Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Tue, 30 Jan 2024 10:40:28 +0100 Subject: [PATCH 095/114] Update quick-ties-stare.md Signed-off-by: Ben Lambert --- .changeset/quick-ties-stare.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-ties-stare.md b/.changeset/quick-ties-stare.md index 137344512b..19eb50feed 100644 --- a/.changeset/quick-ties-stare.md +++ b/.changeset/quick-ties-stare.md @@ -3,4 +3,4 @@ '@backstage/backend-common': patch --- -Support `token` in `readTree` and `readUrl` +Support `token` in `readTree`, `readUrl` and `search` From 4687660941fe11329cdaf3f123c551ef1d50a653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 30 Jan 2024 10:48:46 +0100 Subject: [PATCH 096/114] Update docs/overview/threat-model.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Philipp Hugenroth 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 115c12b307..e1e1afd17e 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -52,7 +52,7 @@ As part of signing in with an identity resolver, a Backstage Token is issued con The token is used to prove the identity of the user within the Backstage system, and is used throughout Backstage plugins to control access. It is important that the ownership resolution logic is consistent across the entire Backstage ecosystem, with no possibility of misinterpreting the ownership information. -For cross-backend communication, the Backstage Token is typically forwarded or, in strict backend-to-backend communication without a user party, the backend itself issues a service token based on a pre-shared secret which is then validated on the receiving end. There are no unique service identities tied to these tokens at this point, meaning the tokens can be used across all services in a Backstage installation, this is something that we aim to improve in the future. +For cross-backend communication, the Backstage Token is typically forwarded or, in strict backend-to-backend communication without a user party, the backend itself issues a service token based on a pre-shared secret which is then validated on the receiving end. There are no unique service identities tied to these tokens at this point, meaning the tokens can be used across all services in a Backstage installation. This is something that we aim to improve in the future. Backstage also supports authentication through a proxy where the user identity is read from the incoming request from the proxy, which has been decorated by an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` does not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. It’s therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider. From b3a06270423604fdbbb36d85811bad7c34eb2624 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 Jan 2024 11:10:51 +0100 Subject: [PATCH 097/114] chore: move the messaging and fix windows verify builds Signed-off-by: blam --- .github/workflows/verify_windows.yml | 2 +- .../src/lib/templating/SecureTemplater.ts | 9 +++++ .../src/lib/templating/helpers.ts | 37 ++++++++++++++++++ .../scaffolder-backend/src/service/helpers.ts | 39 ------------------- .../scaffolder-backend/src/service/router.ts | 16 +------- 5 files changed, 48 insertions(+), 55 deletions(-) create mode 100644 plugins/scaffolder-backend/src/lib/templating/helpers.ts diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 143b22c8b6..5b188770b4 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -21,7 +21,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=4096 + NODE_OPTIONS: ${{ matrix.node-version == '20.x' && '--max-old-space-size=4096 --no-node-snapshot' || '--max-old-space-size=4096' }} INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 607b1c6add..9c5fed3aa3 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -22,6 +22,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; import { JsonValue } from '@backstage/types'; +import { getMajorNodeVersion, isNoNodeSnapshotOptionProvided } from './helpers'; // language=JavaScript const mkScript = (nunjucksSource: string) => ` @@ -128,6 +129,14 @@ export class SecureTemplater { nunjucksConfigs = {}, } = options; + const nodeVersion = getMajorNodeVersion(); + if (nodeVersion >= 20 && !isNoNodeSnapshotOptionProvided()) { + throw new Error( + `When using node v20+ Scaffolder requires that node be started with the --no-node-snapshot option. + Please make sure that you have NODE_OPTIONS=--no-node-snapshot in your environment.`, + ); + } + const isolate = new Isolate({ memoryLimit: 128 }); const context = await isolate.createContext(); const contextGlobal = context.global; diff --git a/plugins/scaffolder-backend/src/lib/templating/helpers.ts b/plugins/scaffolder-backend/src/lib/templating/helpers.ts new file mode 100644 index 0000000000..3dfa4589bf --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/helpers.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function isNoNodeSnapshotOptionProvided(): boolean { + return ( + process.env.NODE_OPTIONS?.includes('--no-node-snapshot') || + process.argv.includes('--no-node-snapshot') + ); +} + +/** + * Gets the major version of the currently running Node.js process. + * + * @remarks + * This function extracts the major version from `process.versions.node` (a string representing the Node.js version), + * which includes the major, minor, and patch versions. It splits this string by the `.` character to get an array + * of these versions, and then parses the first element of this array (the major version) to a number. + * + * @returns {number} The major version of the currently running Node.js process. + */ +export function getMajorNodeVersion(): number { + const version = process.versions.node; + return parseInt(version.split('.')[0], 10); +} diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index 9e271a9d79..3412fec2d3 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -107,42 +107,3 @@ export async function findTemplate(options: { return template as TemplateEntityV1beta3; } - -/** - * Checks if the '--no-node-snapshot' option is included in the NODE_OPTIONS environment variable - * or not included in the command line arguments. - * - * @remarks - * This function checks whether the '--no-node-snapshot' option is part of the NODE_OPTIONS environment - * variable or is missing from the command line arguments. If either condition is met, the function returns `true`. - * This check is especially important when using the "isolated-vm" package with Node.js version 20.x or later. - * - * According to the "isolated-vm" documentation on GitHub (https://github.com/laverdet/isolated-vm), - * if you are using a version of Node.js 20.x or later and you don't pass the '--no-node-snapshot' option, - * it can cause the process to crash. This function helps prevent such crashes by ensuring that the option - * is correctly provided. - * - * @returns {boolean} Returns `true` if the '--no-node-snapshot' option is included in the NODE_OPTIONS - * environment variable or not included in the command line arguments. Otherwise, it returns `false`. - */ -export function isNoNodeSnapshotOptionProvided(): boolean { - return ( - process.env.NODE_OPTIONS?.includes('--no-node-snapshot') || - process.argv.includes('--no-node-snapshot') - ); -} - -/** - * Gets the major version of the currently running Node.js process. - * - * @remarks - * This function extracts the major version from `process.versions.node` (a string representing the Node.js version), - * which includes the major, minor, and patch versions. It splits this string by the `.` character to get an array - * of these versions, and then parses the first element of this array (the major version) to a number. - * - * @returns {number} The major version of the currently running Node.js process. - */ -export function getMajorNodeVersion(): number { - const version = process.versions.node; - return parseInt(version.split('.')[0], 10); -} diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index dec3dcf69a..c0322af283 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -61,13 +61,7 @@ import { } from '../scaffolder'; import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; -import { - findTemplate, - getEntityBaseUrl, - getMajorNodeVersion, - getWorkingDirectory, - isNoNodeSnapshotOptionProvided, -} from './helpers'; +import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; import { IdentityApi, IdentityApiGetIdentityRequest, @@ -266,14 +260,6 @@ export async function createRouter( const logger = parentLogger.child({ plugin: 'scaffolder' }); - const nodeVersion = getMajorNodeVersion(); - if (nodeVersion >= 20 && !isNoNodeSnapshotOptionProvided()) { - throw new Error( - 'When using node v20+ Scaffolder requires that node be started with the --no-node-snapshot option. Please restart ' + - 'Backstage providing the node --no-node-snapshot option.', - ); - } - const identity: IdentityApi = options.identity || buildDefaultIdentityClient(options); const workingDirectory = await getWorkingDirectory(config, logger); From f6792c6758d5562879cfa833bef2dba636d64aa8 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 Jan 2024 11:14:07 +0100 Subject: [PATCH 098/114] chore: updating messaging Signed-off-by: blam --- .changeset/funny-buttons-sip.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/funny-buttons-sip.md diff --git a/.changeset/funny-buttons-sip.md b/.changeset/funny-buttons-sip.md new file mode 100644 index 0000000000..c455b8a31b --- /dev/null +++ b/.changeset/funny-buttons-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Move the `NODE_OPTIONS` messaging for `--no-node-snapshot` to the `SecureTemplater` in order to get better messaging at runtime From 674c9fbf36c5063c145fedece763e38380037bfe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:23:46 +0000 Subject: [PATCH 099/114] fix(deps): update dependency @keyv/redis to v2.8.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1d31e0fc44..79d27b5501 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12178,11 +12178,11 @@ __metadata: linkType: hard "@keyv/redis@npm:^2.5.3": - version: 2.8.3 - resolution: "@keyv/redis@npm:2.8.3" + version: 2.8.4 + resolution: "@keyv/redis@npm:2.8.4" dependencies: ioredis: ^5.3.2 - checksum: 4b8fa4baeab75aace0a8c5d02e30dfb87fa2e27236d8ce3577ff862c4efa2ae2f6d4c14b4933e08fd24fd2719a01d17ac596e20792a1d3c0c2af51a46ba48936 + checksum: 088fb439dc900d6c848c187a0a3218f8c80e3d5df0ec94995d2245b701d59c9d99d4ccc2b48b12682e20d1c0653ababc2f7a51a04737c4df539f4dac82eca87b languageName: node linkType: hard From 26ca6a43424ee621d4f521572f6052680b0859f6 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Tue, 30 Jan 2024 11:34:28 +0100 Subject: [PATCH 100/114] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Ben Lambert Signed-off-by: blam --- .../scaffolder-backend/src/lib/templating/SecureTemplater.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 9c5fed3aa3..14a58e4975 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -132,7 +132,7 @@ export class SecureTemplater { const nodeVersion = getMajorNodeVersion(); if (nodeVersion >= 20 && !isNoNodeSnapshotOptionProvided()) { throw new Error( - `When using node v20+ Scaffolder requires that node be started with the --no-node-snapshot option. + `When using Node.js version 20 or newer, the scaffolder backend plugin requires that it be started with the --no-node-snapshot option. Please make sure that you have NODE_OPTIONS=--no-node-snapshot in your environment.`, ); } From f72b0325cbadcc50dcf03489a00c508785e03f0d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 30 Jan 2024 12:16:31 +0100 Subject: [PATCH 101/114] beps: add Auth Architecture Evolution Signed-off-by: Patrik Oldsberg --- .../README.md | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 beps/0003-auth-architecture-evolution/README.md diff --git a/beps/0003-auth-architecture-evolution/README.md b/beps/0003-auth-architecture-evolution/README.md new file mode 100644 index 0000000000..f70f19e8f9 --- /dev/null +++ b/beps/0003-auth-architecture-evolution/README.md @@ -0,0 +1,141 @@ +--- +title: Auth Architecture Evolution +status: provisional +authors: + - '@Rugvip' +owners: + - '@backstage/maintainers' +project-areas: + - core +creation-date: 2024-01-28 +--- + +# BEP: Auth Architecture Evolution + + + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + +This proposal outlines a new architecture for authenticating users and services in Backstage. It adds built-in access restriction to Backstage instances that protects them from outside access, and enables more granular access control for inter-plugin communication and access from external services. + +It proposes a new `AuthService` interface that handles all uses and service authentication and token management available to plugins, as well as a new `HttpAuthService` interface that is a higher level service used to protect endpoints of plugin routers. The `auth-backend` will also be extended to support issuing of user tokens with a reduced scope for cookie-based authentication of requests. + +The changes to the service-to-service auth are aimed to be the minimal needed to get the necessary interfaces in place, and will rely on the existing symmetrical keys for now. + +## Motivation + +This proposal aims to address several of the points in the [Auth Meta issue](https://github.com/backstage/backstage/issues/15999), with the overarching goal being to replace the existing [API request authentication](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial in `contrib/` with a more robust and secure built-in solution. The tutorial exists for two purposes: to add authentication of API requests as part of using the permission system in Backstage, and to protect a Backstage instance from external access. It does a fairly good job of the former, although we want to avoid placing user tokens in cookies, but it does a quite poor job of the latter, which we want to fix. + +A secondary goal is to do this work before stabilizing the APIs in the new Backend system, as it will have some impact on how plugin backends are built. This will inevitably also lead to the need to improve the way that service-to-service auth it handled in Backstage, although that is not the primary goal of this work. + +By providing protection of Backstage instances out of the box we drastically reduce both the barrier of entry as well as security risks for Backstage adopters. It will no longer be a requirement to either set up protection of your Backstage instance or not do so and risk exposing your instance to malicious actors. It also drastically reduces the complexity of enabling the permission system of Backstage, since access restrictions are already built-in. + +### Goals + +The following goals are the primary focus of this BEP: + +- Built-in protection of Backstage instances such that it is safe to deploy Backstage directly towards the internet. + - Protection of the frontend app bundle from being accessed by unauthenticated users. + - Basic rate limiting of non-authenticated requests. + - Cookie-based authentication of requests for static assets that protects against CSRF attacks and does not unnecessarily expose user tokens. +- Basic improvements to the service-to-service auth service interfaces such that we are confident that we do not need to break them in the near future. + - If possible we will keep using the existing symmetrical keys that are used today, but it is likely that we will need to break compatibility of existing tokens. + +### Non-Goals + +- No advanced rate limiting or other protection against DDoS attacks. If this is a concern then adopters should still use other external technologies to protect access to their Backstage instance. +- As part of the immediate work we will only add as much support for service-to-service auth as is needed for a stable API, and not necessarily make it very capable from the start. + +## Proposal + +Two new backend service interfaces are introduced to support these new features. The new `AuthService` is a low-level service that encapsulates authentication and creation of bearer tokens for all types of Backstage identities, including user, service, and services making requests on behalf of users. The new `HttpAuthService` is a higher level service that lets your create [express middleware](https://expressjs.com/en/guide/using-middleware.html) to protect endpoints, as well as access the credentials and identity of incoming requests. These new services replace the existing `IdentityService` and `TokenManagerService` + +The proposed design leaves the decision for how different endpoints are protected to the implementation of the plugin backends themselves. This includes whether particular routes should allow anonymous access, access from users authenticated via a cookie, or perhaps only allow access from other plugin backends and external services. This means that integrators to not need to and do not have the ability to configure access controls of individual endpoints, except for what the permission system already provides, and what is made available through static configuration or extension points. + +In order to allow for cookie-based authentication of incoming users requests, the `auth` plugin backend is extended to be able to issue users tokens with reduced scope, which in turn integrate with the new `AuthService` and `HttpAuthService`. The ability to use cookie auth for request is opt-in per route and is only be permitted for read methods (`GET`, `HEAD`, `OPTIONS`). The actual implementation of cookie-based flows will be up to each plugin, but with significant help from the new auth service interfaces. + +For service-to-service communication we will move away from reusing user tokens in upstream requests. We will instead implement an "On-Behalf-Of" flow where incoming user tokens are encapsulated in a service credential for the upstream request. In line with this the new auth service interfaces will aim to make it difficult to directly forward credentials from incoming requests, and instead encourage that plugin backends issue new service credentials for upstream requests. + +## Design Details + +### AuthService (WIP) + +The new `AuthService` interface is defined as follows: + +```ts +export type BackstageCredentials = { + token: string; + + user?: { + userEntityRef: string; + ownershipEntityRefs: string[]; + }; + + service?: { + id: string; + }; +}; + +export interface AuthService { + authenticate(token: string): Promise; + + issueToken(credentials: BackstageCredentials): Promise<{ token: string }>; +} +``` + +TODO: add usage patterns for this service + +### HttpAuthService (WIP) + +The new `HttpAuthService` interface is defined as follows: + +```ts +type AuthTypes = 'user' | 'user-cookie' | 'service' | 'unauthorized'; + +export interface HttpAuthServiceMiddlewareOptions { + allow: AuthTypes[]; +} + +export interface HttpAuthService { + createHttpPluginRouterMiddleware(): Handler; + + middleware(options?: HttpAuthServiceMiddlewareOptions): Handler; + + credentials( + req: Request, + options?: HttpAuthServiceMiddlewareOptions, + ): BackstageCredentials; + + requestHeaders(credentials: BackstageCredentials): Record; + + issueUserCookie(res: Response): Promise; +} +``` + +TODO: add usage patterns for this service + +## Release Plan + +The existing `IdentityService` and `TokenManagerService` will be deprecated and instead implemented in terms of the new `AuthService`. + +The release plan for the `HttpAuthService` is TBD, but is likely to be shipped as a no-op for plugins using the old backend system. + +## Dependencies + +No significant dependencies have been identified for this work, although any future security audits of Backstage are considered dependent on this work. + +## Alternatives + +An alternative to built-in protection from external access would be to keep relying on external mechanisms to protect access to Backstage. We feel that this is a suboptimal solution since it adds complexity to the adoption of Backstage, and increases the risk of misconfiguration and security breaches. Regardless of whether we add built-in protection or not the ability to protect API endpoints needs to be addressed in some way, since it is a requirement for the permission system to work. This means that the extra steps to ensure protection out of the box are fairly minimal when looking at just the delta for protecting API access. From 3fe92995fd5bac066ea0210fca5744fb4928dfce Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 25 Jan 2024 09:18:20 +0100 Subject: [PATCH 102/114] feat(analytics-module-ga4): add support to the new analytics api Signed-off-by: Camila Belo --- plugins/analytics-module-ga4/package.json | 1 + .../AnalyticsApi/GoogleAnalytics4.test.ts | 38 +++++++++++++++++++ .../AnalyticsApi/GoogleAnalytics4.ts | 21 +++++++--- yarn.lock | 1 + 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/plugins/analytics-module-ga4/package.json b/plugins/analytics-module-ga4/package.json index 97b610c2bf..89113efe6d 100644 --- a/plugins/analytics-module-ga4/package.json +++ b/plugins/analytics-module-ga4/package.json @@ -32,6 +32,7 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "react-ga4": "^2.0.0" }, "peerDependencies": { diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts index 16db7075b3..5bae530b2b 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts @@ -492,4 +492,42 @@ describe('GoogleAnalytics4', () => { }); }); }); + + describe('Backward compatibility', () => { + it('fallback category for legacy context extension property', () => { + const api = GoogleAnalytics4.fromConfig(basicValidConfig); + + expect(api.captureEvent).toBeDefined(); + + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + + expect(fnEvent).toHaveBeenCalledWith('page_view', { + action: 'page_view', + label: '/', + category: 'App', + }); + }); + + it('prioritize new context extension id over old extension property', () => { + const api = GoogleAnalytics4.fromConfig(basicValidConfig); + + expect(api.captureEvent).toBeDefined(); + + api.captureEvent({ + action: 'navigate', + subject: '/', + context: { ...context, extensionId: 'App', extension: '' }, + }); + + expect(fnEvent).toHaveBeenCalledWith('page_view', { + action: 'page_view', + label: '/', + category: 'App', + }); + }); + }); }); diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts index 9abf4719c6..3d2e633962 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import ReactGA from 'react-ga4'; import { AnalyticsApi, @@ -21,6 +22,11 @@ import { AnalyticsEvent, IdentityApi, } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyticsApi, + AnalyticsEvent as NewAnalyticsEvent, + AnalyticsContextValue as NewAnalyticsContextValue, +} from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { DeferredCapture } from '../../../util/DeferredCapture'; @@ -28,7 +34,7 @@ import { DeferredCapture } from '../../../util/DeferredCapture'; * Google Analytics API provider for the Backstage Analytics API. * @public */ -export class GoogleAnalytics4 implements AnalyticsApi { +export class GoogleAnalytics4 implements AnalyticsApi, NewAnalyticsApi { private readonly customUserIdTransform?: ( userEntityRef: string, ) => Promise; @@ -157,17 +163,20 @@ export class GoogleAnalytics4 implements AnalyticsApi { * applied as they should be (set on pageview, merged object on events). * @param event - AnalyticsEvent type captured */ - captureEvent(event: AnalyticsEvent) { + captureEvent(event: AnalyticsEvent | NewAnalyticsEvent) { const { context, action, subject, value, attributes } = event; const customEventData = this.setEventParameters(context, attributes); if (this.contentGroupBy) { customEventData.content_group = context[this.contentGroupBy]!; } - if (action === 'navigate' && context.extension === 'App') { + const extensionId = context.extensionId || context.extension; + const category = extensionId ? String(extensionId) : 'App'; + + if (action === 'navigate' && category === 'App') { this.capture.event( { - category: context.extension || 'App', + category, action: 'page_view', label: subject, value, @@ -183,7 +192,7 @@ export class GoogleAnalytics4 implements AnalyticsApi { this.capture.event( { - category: context.extension || 'App', + category, action, label: subject, value, @@ -199,7 +208,7 @@ export class GoogleAnalytics4 implements AnalyticsApi { * @param attributes additional analytics event attributes */ private setEventParameters( - context: AnalyticsContextValue, + context: AnalyticsContextValue | NewAnalyticsContextValue, attributes: AnalyticsEventAttributes = {}, ) { const customEventParameters: { diff --git a/yarn.lock b/yarn.lock index 79d27b5501..5f26cb9a82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4312,6 +4312,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@testing-library/dom": ^9.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^14.0.0 From 87f3d69dc50bdd5af49c02153a4c7a86daa9bab2 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 25 Jan 2024 09:19:27 +0100 Subject: [PATCH 103/114] fix(frontend-app-api): add global analytics context to the app extension Signed-off-by: Camila Belo --- packages/frontend-app-api/src/extensions/App.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/frontend-app-api/src/extensions/App.tsx b/packages/frontend-app-api/src/extensions/App.tsx index 751a255fa7..82e7600178 100644 --- a/packages/frontend-app-api/src/extensions/App.tsx +++ b/packages/frontend-app-api/src/extensions/App.tsx @@ -14,7 +14,9 @@ * limitations under the License. */ +import React from 'react'; import { + ExtensionBoundary, coreExtensionData, createApiExtension, createComponentExtension, @@ -50,9 +52,13 @@ export const App = createExtension({ output: { root: coreExtensionData.reactElement, }, - factory({ inputs }) { + factory({ node, inputs }) { return { - root: inputs.root.output.element, + root: ( + + {inputs.root.output.element} + + ), }; }, }); From 01abfa9c2b5884c2ccbd1deb3aa729fe6202a3e8 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 25 Jan 2024 09:22:26 +0100 Subject: [PATCH 104/114] feat(frontend-plugin-api): fix default app plugin id Signed-off-by: Camila Belo --- .../frontend-plugin-api/src/analytics/AnalyticsContext.tsx | 2 +- packages/frontend-plugin-api/src/analytics/types.ts | 3 --- .../frontend-plugin-api/src/components/ExtensionBoundary.tsx | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx index 10ef475803..2c022ad8e9 100644 --- a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx +++ b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx @@ -37,7 +37,7 @@ export const useAnalyticsContext = (): AnalyticsContextValue => { // Provide a default value if no value exists. if (theContext === undefined) { return { - pluginId: 'root', + pluginId: 'app', extensionId: 'App', }; } diff --git a/packages/frontend-plugin-api/src/analytics/types.ts b/packages/frontend-plugin-api/src/analytics/types.ts index 21347e2fc7..3e3a9ad5eb 100644 --- a/packages/frontend-plugin-api/src/analytics/types.ts +++ b/packages/frontend-plugin-api/src/analytics/types.ts @@ -25,9 +25,6 @@ export type CommonAnalyticsContext = { */ pluginId: string; - /** - * The nearest known parent extension where the event was captured. - */ /** * The nearest known parent extension where the event was captured. */ diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 9a8eb5b750..768352b76e 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -65,7 +65,7 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { // Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight const attributes = { extensionId: node.spec.id, - pluginId: node.spec.source?.id, + pluginId: node.spec.source?.id ?? 'app', }; return ( From b1afb710b8cf23c17c112d4161e983419509b547 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 25 Jan 2024 11:04:20 +0100 Subject: [PATCH 105/114] fix(frontend-plugin-api): default api config error Signed-off-by: Camila Belo --- packages/core-plugin-api/package.json | 1 + .../src/apis/system/useApi.tsx | 11 +++++--- .../src/routing/RouteTracker.test.tsx | 8 +++--- .../src/analytics/AnalyticsContext.test.tsx | 10 +++---- .../src/analytics/AnalyticsContext.tsx | 2 +- .../src/analytics/useAnalytics.test.tsx | 4 +-- .../src/analytics/useAnalytics.tsx | 7 +++-- .../AnalyticsApi/GoogleAnalytics4.test.ts | 26 ++++++++++++++++--- .../AnalyticsApi/GoogleAnalytics4.ts | 5 ++-- yarn.lock | 1 + 10 files changed, 51 insertions(+), 24 deletions(-) diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index d676260227..68d5ad68c3 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -47,6 +47,7 @@ }, "dependencies": { "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "@types/react": "^16.13.1 || ^17.0.0", diff --git a/packages/core-plugin-api/src/apis/system/useApi.tsx b/packages/core-plugin-api/src/apis/system/useApi.tsx index 03c4fa33ac..2bb1532525 100644 --- a/packages/core-plugin-api/src/apis/system/useApi.tsx +++ b/packages/core-plugin-api/src/apis/system/useApi.tsx @@ -17,6 +17,7 @@ import React, { PropsWithChildren } from 'react'; import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; import { useVersionedContext } from '@backstage/version-bridge'; +import { NotImplementedError } from '@backstage/errors'; /** * React hook for retrieving {@link ApiHolder}, an API catalog. @@ -26,12 +27,12 @@ import { useVersionedContext } from '@backstage/version-bridge'; export function useApiHolder(): ApiHolder { const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); if (!versionedHolder) { - throw new Error('API context is not available'); + throw new NotImplementedError('API context is not available'); } const apiHolder = versionedHolder.atVersion(1); if (!apiHolder) { - throw new Error('ApiContext v1 not available'); + throw new NotImplementedError('ApiContext v1 not available'); } return apiHolder; } @@ -47,7 +48,7 @@ export function useApi(apiRef: ApiRef): T { const api = apiHolder.get(apiRef); if (!api) { - throw new Error(`No implementation available for ${apiRef}`); + throw new NotImplementedError(`No implementation available for ${apiRef}`); } return api; } @@ -73,7 +74,9 @@ export function withApis(apis: TypesToApiRefs) { const api = apiHolder.get(ref); if (!api) { - throw new Error(`No implementation available for ${ref}`); + throw new NotImplementedError( + `No implementation available for ${ref}`, + ); } impls[key] = api; } diff --git a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx index 0e66d0e50d..935449b697 100644 --- a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx +++ b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx @@ -211,8 +211,8 @@ describe('RouteTracker', () => { action: 'navigate', attributes: {}, context: { - extensionId: 'App', - pluginId: 'root', + extensionId: 'app', + pluginId: 'app', }, subject: '/not-routable-extension', value: undefined, @@ -221,8 +221,8 @@ describe('RouteTracker', () => { action: 'click', attributes: undefined, context: { - extensionId: 'App', - pluginId: 'root', + extensionId: 'app', + pluginId: 'app', }, subject: 'test', value: undefined, diff --git a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx index 8e78bdc8c4..bdfdef3005 100644 --- a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx +++ b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx @@ -35,8 +35,8 @@ describe('AnalyticsContext', () => { it('returns default values', () => { const { result } = renderHook(() => useAnalyticsContext()); expect(result.current).toEqual({ - extensionId: 'App', - pluginId: 'root', + extensionId: 'app', + pluginId: 'app', }); }); }); @@ -49,8 +49,8 @@ describe('AnalyticsContext', () => { , ); - expect(result.getByTestId('extension-id')).toHaveTextContent('App'); - expect(result.getByTestId('plugin-id')).toHaveTextContent('root'); + expect(result.getByTestId('extension-id')).toHaveTextContent('app'); + expect(result.getByTestId('plugin-id')).toHaveTextContent('app'); }); it('uses provided analytics context', () => { @@ -60,7 +60,7 @@ describe('AnalyticsContext', () => { , ); - expect(result.getByTestId('extension-id')).toHaveTextContent('App'); + expect(result.getByTestId('extension-id')).toHaveTextContent('app'); expect(result.getByTestId('plugin-id')).toHaveTextContent('custom'); }); diff --git a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx index 2c022ad8e9..13047b41d7 100644 --- a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx +++ b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx @@ -38,7 +38,7 @@ export const useAnalyticsContext = (): AnalyticsContextValue => { if (theContext === undefined) { return { pluginId: 'app', - extensionId: 'App', + extensionId: 'app', }; } diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx index beb16b1fbb..6dbcc81f11 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx @@ -53,8 +53,8 @@ describe('useAnalytics', () => { some: 'value', }, context: { - extensionId: 'App', - pluginId: 'root', + extensionId: 'app', + pluginId: 'app', }, }); }); diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx index f3b5998de4..397906d1d1 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx @@ -23,8 +23,11 @@ import { Tracker } from './Tracker'; function useAnalyticsApi(): AnalyticsApi { try { return useApi(analyticsApiRef); - } catch { - return { captureEvent: () => {} }; + } catch (error) { + if (error.name === 'NotImplementedError') { + return { captureEvent: () => {} }; + } + throw error; } } diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts index 5bae530b2b..93a6e6fe6a 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts @@ -493,8 +493,8 @@ describe('GoogleAnalytics4', () => { }); }); - describe('Backward compatibility', () => { - it('fallback category for legacy context extension property', () => { + describe('api backward compatibility', () => { + it('continue working with legacy App category', () => { const api = GoogleAnalytics4.fromConfig(basicValidConfig); expect(api.captureEvent).toBeDefined(); @@ -512,6 +512,24 @@ describe('GoogleAnalytics4', () => { }); }); + it('use lowercase app as the new default category', () => { + const api = GoogleAnalytics4.fromConfig(basicValidConfig); + + expect(api.captureEvent).toBeDefined(); + + api.captureEvent({ + action: 'navigate', + subject: '/', + context: { ...context, extensionId: '', extension: '' }, + }); + + expect(fnEvent).toHaveBeenCalledWith('page_view', { + action: 'page_view', + label: '/', + category: 'app', + }); + }); + it('prioritize new context extension id over old extension property', () => { const api = GoogleAnalytics4.fromConfig(basicValidConfig); @@ -520,13 +538,13 @@ describe('GoogleAnalytics4', () => { api.captureEvent({ action: 'navigate', subject: '/', - context: { ...context, extensionId: 'App', extension: '' }, + context: { ...context, extensionId: 'app', extension: '' }, }); expect(fnEvent).toHaveBeenCalledWith('page_view', { action: 'page_view', label: '/', - category: 'App', + category: 'app', }); }); }); diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts index 3d2e633962..1f4cbfeee5 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts @@ -171,9 +171,10 @@ export class GoogleAnalytics4 implements AnalyticsApi, NewAnalyticsApi { } const extensionId = context.extensionId || context.extension; - const category = extensionId ? String(extensionId) : 'App'; + const category = extensionId ? String(extensionId) : 'app'; - if (action === 'navigate' && category === 'App') { + // The legacy default extension was 'App' and the new one is 'app' + if (action === 'navigate' && category.toLocaleLowerCase() === 'app') { this.capture.event( { category, diff --git a/yarn.lock b/yarn.lock index 5f26cb9a82..efac8bb495 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3896,6 +3896,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" From 405702ba8b04d6065b609897a5dbbe2480ebe739 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 26 Jan 2024 09:22:21 +0100 Subject: [PATCH 106/114] feat: add api backward compatibility for more analytics modules Signed-off-by: Camila Belo --- plugins/analytics-module-ga/package.json | 1 + .../AnalyticsApi/GoogleAnalytics.test.ts | 59 +++++++++++++++++++ .../AnalyticsApi/GoogleAnalytics.ts | 19 ++++-- .../package.json | 1 + .../AnalyticsApi/NewRelicBrowser.ts | 15 ++++- yarn.lock | 2 + 6 files changed, 89 insertions(+), 8 deletions(-) diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 3d367a5889..cabff881ad 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -32,6 +32,7 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "react-ga": "^3.3.0" }, "peerDependencies": { diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts index 65e9256aa8..1380f9c715 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts @@ -508,4 +508,63 @@ describe('GoogleAnalytics', () => { expect(lastData.queueTime).toBeUndefined(); }); }); + + describe('api backward compatibility', () => { + it('continue working with legacy App category', () => { + const api = GoogleAnalytics.fromConfig(basicValidConfig); + + expect(api.captureEvent).toBeDefined(); + + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'pageview', + page: '/', + }); + }); + + it('use lowercase app as the new default category', () => { + const api = GoogleAnalytics.fromConfig(basicValidConfig); + + expect(api.captureEvent).toBeDefined(); + + api.captureEvent({ + action: 'navigate', + subject: '/', + context: { ...context, extensionId: '', extension: '' }, + }); + + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'pageview', + page: '/', + }); + }); + + it('prioritize new context extension id over old extension property', () => { + const api = GoogleAnalytics.fromConfig(basicValidConfig); + + expect(api.captureEvent).toBeDefined(); + + api.captureEvent({ + action: 'navigate', + subject: '/', + context: { ...context, extensionId: 'app', extension: '' }, + }); + + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'pageview', + page: '/', + }); + }); + }); }); diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts index 17c5b26be1..045bc94f3a 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts @@ -22,6 +22,11 @@ import { AnalyticsEventAttributes, IdentityApi, } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyticsApi, + AnalyticsEvent as NewAnalyticsEvent, + AnalyticsContextValue as NewAnalyticsContextValue, +} from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { DeferredCapture } from '../../../util'; import { @@ -40,7 +45,7 @@ type CustomDimensionOrMetricConfig = { * Google Analytics API provider for the Backstage Analytics API. * @public */ -export class GoogleAnalytics implements AnalyticsApi { +export class GoogleAnalytics implements AnalyticsApi, NewAnalyticsApi { private readonly cdmConfig: CustomDimensionOrMetricConfig[]; private customUserIdTransform?: (userEntityRef: string) => Promise; private readonly capture: DeferredCapture; @@ -157,11 +162,15 @@ export class GoogleAnalytics implements AnalyticsApi { * pageview and the rest as custom events. All custom dimensions/metrics are * applied as they should be (set on pageview, merged object on events). */ - captureEvent(event: AnalyticsEvent) { + captureEvent(event: AnalyticsEvent | NewAnalyticsEvent) { const { context, action, subject, value, attributes } = event; const customMetadata = this.getCustomDimensionMetrics(context, attributes); - if (action === 'navigate' && context.extension === 'App') { + const extensionId = context.extensionId || context.extension; + const category = extensionId ? String(extensionId) : 'app'; + + // The legacy default extension was 'App' and the new one is 'app' + if (action === 'navigate' && category.toLocaleLowerCase() === 'app') { this.capture.pageview(subject, customMetadata); return; } @@ -184,7 +193,7 @@ export class GoogleAnalytics implements AnalyticsApi { } this.capture.event({ - category: context.extension || 'App', + category, action, label: subject, value, @@ -197,7 +206,7 @@ export class GoogleAnalytics implements AnalyticsApi { * Event Attributes, e.g. { dimension1: "some value", metric8: 42 } */ private getCustomDimensionMetrics( - context: AnalyticsContextValue, + context: AnalyticsContextValue | NewAnalyticsContextValue, attributes: AnalyticsEventAttributes = {}, ) { const customDimensionsMetrics: { [x: string]: string | number | boolean } = diff --git a/plugins/analytics-module-newrelic-browser/package.json b/plugins/analytics-module-newrelic-browser/package.json index 0815b62b6c..44610dd026 100644 --- a/plugins/analytics-module-newrelic-browser/package.json +++ b/plugins/analytics-module-newrelic-browser/package.json @@ -26,6 +26,7 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@newrelic/browser-agent": "^1.236.0" }, "peerDependencies": { diff --git a/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts b/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts index f2f35b5765..ba04aea322 100644 --- a/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts +++ b/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts @@ -19,6 +19,10 @@ import { IdentityApi, AnalyticsEvent, } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyicsApi, + AnalyticsEvent as NewAnalyticsEvent, +} from '@backstage/frontend-plugin-api'; import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent'; import type { setAPI } from '@newrelic/browser-agent/loaders/api/api'; @@ -37,7 +41,7 @@ type NewRelicBrowserOptions = { * New Relic Browser API provider for the Backstage Analytics API. * @public */ -export class NewRelicBrowser implements AnalyticsApi { +export class NewRelicBrowser implements AnalyticsApi, NewAnalyicsApi { private readonly agent: NewRelicAPI; private constructor( @@ -121,9 +125,14 @@ export class NewRelicBrowser implements AnalyticsApi { ); } - captureEvent(event: AnalyticsEvent) { + captureEvent(event: AnalyticsEvent | NewAnalyticsEvent) { const { context, action, subject, value, attributes } = event; - if (action === 'navigate' && context.extension === 'App') { + + const extensionId = context.extensionId || context.extension; + const category = extensionId ? String(extensionId) : 'app'; + + // The legacy default extension was 'App' and the new one is 'app' + if (action === 'navigate' && category.toLocaleLowerCase() === 'app') { const interaction = this.agent.interaction(); interaction.setName(subject); if (value) { diff --git a/yarn.lock b/yarn.lock index efac8bb495..41ceb79882 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4336,6 +4336,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@testing-library/dom": ^9.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^14.0.0 @@ -4357,6 +4358,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@newrelic/browser-agent": ^1.236.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^14.0.0 From e586f797026c86e44a16a7585cc7e71a4332c521 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 26 Jan 2024 09:39:32 +0100 Subject: [PATCH 107/114] docs: add changeset files Signed-off-by: Camila Belo --- .changeset/blue-keys-do.md | 5 +++++ .changeset/gorgeous-pumas-draw.md | 7 +++++++ .changeset/old-papayas-shave.md | 5 +++++ .changeset/shaggy-trainers-rule.md | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 .changeset/blue-keys-do.md create mode 100644 .changeset/gorgeous-pumas-draw.md create mode 100644 .changeset/old-papayas-shave.md create mode 100644 .changeset/shaggy-trainers-rule.md diff --git a/.changeset/blue-keys-do.md b/.changeset/blue-keys-do.md new file mode 100644 index 0000000000..c933bac0f2 --- /dev/null +++ b/.changeset/blue-keys-do.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root`. diff --git a/.changeset/gorgeous-pumas-draw.md b/.changeset/gorgeous-pumas-draw.md new file mode 100644 index 0000000000..d2ae0370e5 --- /dev/null +++ b/.changeset/gorgeous-pumas-draw.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-analytics-module-newrelic-browser': minor +'@backstage/plugin-analytics-module-ga4': minor +'@backstage/plugin-analytics-module-ga': minor +--- + +**BREAKING**: Add support to the new analytics api and use `app` as default extension and plugin ids. It is breaking only because you will start seeing app as the new default id for extensions and plugins events. diff --git a/.changeset/old-papayas-shave.md b/.changeset/old-papayas-shave.md new file mode 100644 index 0000000000..1372b9ce85 --- /dev/null +++ b/.changeset/old-papayas-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Wrap the root element with the analytics context to ensure it always exists for all extensions. diff --git a/.changeset/shaggy-trainers-rule.md b/.changeset/shaggy-trainers-rule.md new file mode 100644 index 0000000000..4140a400e1 --- /dev/null +++ b/.changeset/shaggy-trainers-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': patch +--- + +Throw a more specific exception `NotImplementedError` when an API implementation cannot be found. From e92f21f27a964b4677d53a66b10ddacd4f8cbebc Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 26 Jan 2024 10:12:04 +0100 Subject: [PATCH 108/114] docs: update api reports Signed-off-by: Camila Belo --- plugins/analytics-module-ga/api-report.md | 6 ++++-- plugins/analytics-module-ga4/api-report.md | 6 ++++-- plugins/analytics-module-newrelic-browser/api-report.md | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/analytics-module-ga/api-report.md b/plugins/analytics-module-ga/api-report.md index 1846211e72..5404248938 100644 --- a/plugins/analytics-module-ga/api-report.md +++ b/plugins/analytics-module-ga/api-report.md @@ -4,7 +4,9 @@ ```ts import { AnalyticsApi } from '@backstage/core-plugin-api'; +import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; +import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Config } from '@backstage/config'; import { IdentityApi } from '@backstage/core-plugin-api'; @@ -13,8 +15,8 @@ import { IdentityApi } from '@backstage/core-plugin-api'; export const analyticsModuleGA: BackstagePlugin<{}, {}>; // @public -export class GoogleAnalytics implements AnalyticsApi { - captureEvent(event: AnalyticsEvent): void; +export class GoogleAnalytics implements AnalyticsApi, AnalyticsApi_2 { + captureEvent(event: AnalyticsEvent | AnalyticsEvent_2): void; static fromConfig( config: Config, options?: { diff --git a/plugins/analytics-module-ga4/api-report.md b/plugins/analytics-module-ga4/api-report.md index 77f3094210..b8282e834a 100644 --- a/plugins/analytics-module-ga4/api-report.md +++ b/plugins/analytics-module-ga4/api-report.md @@ -4,13 +4,15 @@ ```ts import { AnalyticsApi } from '@backstage/core-plugin-api'; +import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; +import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { IdentityApi } from '@backstage/core-plugin-api'; // @public -export class GoogleAnalytics4 implements AnalyticsApi { - captureEvent(event: AnalyticsEvent): void; +export class GoogleAnalytics4 implements AnalyticsApi, AnalyticsApi_2 { + captureEvent(event: AnalyticsEvent | AnalyticsEvent_2): void; static fromConfig( config: Config, options?: { diff --git a/plugins/analytics-module-newrelic-browser/api-report.md b/plugins/analytics-module-newrelic-browser/api-report.md index 7556bae317..09fbf8f0f3 100644 --- a/plugins/analytics-module-newrelic-browser/api-report.md +++ b/plugins/analytics-module-newrelic-browser/api-report.md @@ -4,14 +4,16 @@ ```ts import { AnalyticsApi } from '@backstage/core-plugin-api'; +import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; +import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { IdentityApi } from '@backstage/core-plugin-api'; // @public -export class NewRelicBrowser implements AnalyticsApi { +export class NewRelicBrowser implements AnalyticsApi, AnalyticsApi_2 { // (undocumented) - captureEvent(event: AnalyticsEvent): void; + captureEvent(event: AnalyticsEvent | AnalyticsEvent_2): void; // (undocumented) static fromConfig( config: Config, From 915773bf0a28f4bc9889e4ba95f39a39755666b6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 26 Jan 2024 13:37:35 +0100 Subject: [PATCH 109/114] test(analytics-module-ga): apply review suggestion Signed-off-by: Camila Belo --- .../AnalyticsApi/GoogleAnalytics.test.ts | 80 +++++++++++++++++-- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts index 1380f9c715..056efdb3e6 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts @@ -521,12 +521,30 @@ describe('GoogleAnalytics', () => { context, }); - const [command, data] = ReactGA.testModeAPI.calls[1]; + let [command, data] = ReactGA.testModeAPI.calls[1]; expect(command).toBe('send'); expect(data).toMatchObject({ hitType: 'pageview', page: '/', }); + + api.captureEvent({ + action: 'click', + subject: 'on something', + value: 42, + context, + }); + + [command, data] = ReactGA.testModeAPI.calls[2]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'event', + // expect to use the legacy default category + eventCategory: 'App', + eventAction: 'click', + eventLabel: 'on something', + eventValue: 42, + }); }); it('use lowercase app as the new default category', () => { @@ -537,15 +555,41 @@ describe('GoogleAnalytics', () => { api.captureEvent({ action: 'navigate', subject: '/', - context: { ...context, extensionId: '', extension: '' }, + context: { + ...context, + extensionId: '', + extension: '', + }, }); - const [command, data] = ReactGA.testModeAPI.calls[1]; + let [command, data] = ReactGA.testModeAPI.calls[1]; expect(command).toBe('send'); expect(data).toMatchObject({ hitType: 'pageview', page: '/', }); + + api.captureEvent({ + action: 'click', + subject: 'on something', + value: 42, + context: { + ...context, + extensionId: '', + extension: '', + }, + }); + + [command, data] = ReactGA.testModeAPI.calls[2]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'event', + // expect to use the new default category + eventCategory: 'app', + eventAction: 'click', + eventLabel: 'on something', + eventValue: 42, + }); }); it('prioritize new context extension id over old extension property', () => { @@ -556,15 +600,41 @@ describe('GoogleAnalytics', () => { api.captureEvent({ action: 'navigate', subject: '/', - context: { ...context, extensionId: 'app', extension: '' }, + context: { + ...context, + extensionId: 'app', + extension: '', + }, }); - const [command, data] = ReactGA.testModeAPI.calls[1]; + let [command, data] = ReactGA.testModeAPI.calls[1]; expect(command).toBe('send'); expect(data).toMatchObject({ hitType: 'pageview', page: '/', }); + + api.captureEvent({ + action: 'click', + subject: 'on something', + value: 42, + context: { + ...context, + extensionId: 'page:index', + extension: '', + }, + }); + + [command, data] = ReactGA.testModeAPI.calls[2]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'event', + // expect use the new context extension id + eventCategory: 'page:index', + eventAction: 'click', + eventLabel: 'on something', + eventValue: 42, + }); }); }); }); From b5b9b8d98ae74e5db0867f8dfe3902e74d675591 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 11:30:33 +0100 Subject: [PATCH 110/114] fix: analytics catagory locale Signed-off-by: Camila Belo Co-authored-by: Patrik Oldsberg --- .../implementations/AnalyticsApi/GoogleAnalytics.test.ts | 2 +- .../apis/implementations/AnalyticsApi/GoogleAnalytics.ts | 7 +++++-- .../implementations/AnalyticsApi/GoogleAnalytics4.test.ts | 2 +- .../apis/implementations/AnalyticsApi/GoogleAnalytics4.ts | 7 +++++-- .../apis/implementations/AnalyticsApi/NewRelicBrowser.ts | 7 +++++-- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts index 056efdb3e6..48e43c2a45 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts @@ -585,7 +585,7 @@ describe('GoogleAnalytics', () => { expect(data).toMatchObject({ hitType: 'event', // expect to use the new default category - eventCategory: 'app', + eventCategory: 'App', eventAction: 'click', eventLabel: 'on something', eventValue: 42, diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts index 045bc94f3a..d241cd9860 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts @@ -167,10 +167,13 @@ export class GoogleAnalytics implements AnalyticsApi, NewAnalyticsApi { const customMetadata = this.getCustomDimensionMetrics(context, attributes); const extensionId = context.extensionId || context.extension; - const category = extensionId ? String(extensionId) : 'app'; + const category = extensionId ? String(extensionId) : 'App'; // The legacy default extension was 'App' and the new one is 'app' - if (action === 'navigate' && category.toLocaleLowerCase() === 'app') { + if ( + action === 'navigate' && + category.toLocaleLowerCase('en-US').startsWith('app') + ) { this.capture.pageview(subject, customMetadata); return; } diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts index 93a6e6fe6a..025462903b 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.test.ts @@ -526,7 +526,7 @@ describe('GoogleAnalytics4', () => { expect(fnEvent).toHaveBeenCalledWith('page_view', { action: 'page_view', label: '/', - category: 'app', + category: 'App', }); }); diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts index 1f4cbfeee5..43ff47b84e 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts @@ -171,10 +171,13 @@ export class GoogleAnalytics4 implements AnalyticsApi, NewAnalyticsApi { } const extensionId = context.extensionId || context.extension; - const category = extensionId ? String(extensionId) : 'app'; + const category = extensionId ? String(extensionId) : 'App'; // The legacy default extension was 'App' and the new one is 'app' - if (action === 'navigate' && category.toLocaleLowerCase() === 'app') { + if ( + action === 'navigate' && + category.toLocaleLowerCase('en-US').startsWith('app') + ) { this.capture.event( { category, diff --git a/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts b/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts index ba04aea322..d65d02db64 100644 --- a/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts +++ b/plugins/analytics-module-newrelic-browser/src/apis/implementations/AnalyticsApi/NewRelicBrowser.ts @@ -129,10 +129,13 @@ export class NewRelicBrowser implements AnalyticsApi, NewAnalyicsApi { const { context, action, subject, value, attributes } = event; const extensionId = context.extensionId || context.extension; - const category = extensionId ? String(extensionId) : 'app'; + const category = extensionId ? String(extensionId) : 'App'; // The legacy default extension was 'App' and the new one is 'app' - if (action === 'navigate' && category.toLocaleLowerCase() === 'app') { + if ( + action === 'navigate' && + category.toLocaleLowerCase('en-US').startsWith('app') + ) { const interaction = this.agent.interaction(); interaction.setName(subject); if (value) { From 311dda9e18df096cbffdcecaad08f628d3d154cf Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 11:31:46 +0100 Subject: [PATCH 111/114] feat: add backward compatibility for core anlytics api implementations Signed-off-by: Camila Belo --- .changeset/blue-keys-do.md | 2 +- .changeset/gorgeous-pumas-draw.md | 2 +- packages/frontend-plugin-api/api-report.md | 16 ++++ .../AnalyticsApi/MultipleAnalyticsApi.test.ts | 64 ++++++++++++++++ .../AnalyticsApi/MultipleAnalyticsApi.ts | 76 +++++++++++++++++++ .../AnalyticsApi/NoOpAnalyticsApi.ts | 30 ++++++++ .../implementations/AnalyticsApi/index.ts | 18 +++++ .../src/apis/implementations/index.ts | 20 +++++ .../frontend-plugin-api/src/apis/index.ts | 1 + 9 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/index.ts diff --git a/.changeset/blue-keys-do.md b/.changeset/blue-keys-do.md index c933bac0f2..81e79a7975 100644 --- a/.changeset/blue-keys-do.md +++ b/.changeset/blue-keys-do.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': minor --- -**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root`. +**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root` and update the `MultipleAnalyticsApi` to support new Analytics API context type . diff --git a/.changeset/gorgeous-pumas-draw.md b/.changeset/gorgeous-pumas-draw.md index d2ae0370e5..c5b2214829 100644 --- a/.changeset/gorgeous-pumas-draw.md +++ b/.changeset/gorgeous-pumas-draw.md @@ -4,4 +4,4 @@ '@backstage/plugin-analytics-module-ga': minor --- -**BREAKING**: Add support to the new analytics api and use `app` as default extension and plugin ids. It is breaking only because you will start seeing app as the new default id for extensions and plugins events. +Add support to the new analytics api. diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 4116469c2a..21d329fae8 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -8,6 +8,8 @@ import { AlertApi } from '@backstage/core-plugin-api'; import { alertApiRef } from '@backstage/core-plugin-api'; import { AlertMessage } from '@backstage/core-plugin-api'; +import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/core-plugin-api'; +import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/core-plugin-api'; import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; import { ApiFactory } from '@backstage/core-plugin-api'; @@ -993,6 +995,20 @@ export { identityApiRef }; export { microsoftAuthApiRef }; +// @public +export class MultipleAnalyticsApi implements AnalyticsApi_2, AnalyticsApi { + captureEvent(event: AnalyticsEvent_2 | AnalyticsEvent): void; + static fromApis( + actualApis: (AnalyticsApi_2 | AnalyticsApi)[], + ): MultipleAnalyticsApi; +} + +// @public +export class NoOpAnalyticsApi implements AnalyticsApi_2, AnalyticsApi { + // (undocumented) + captureEvent(_event: AnalyticsEvent_2 | AnalyticsEvent): void; +} + export { OAuthApi }; export { OAuthRequestApi }; diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts new file mode 100644 index 0000000000..8095ee0020 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { MultipleAnalyticsApi } from './MultipleAnalyticsApi'; + +describe('MultipleAnalyticsApi', () => { + const analyticsApiOne = { captureEvent: jest.fn() }; + const analyticsApiTwo = { captureEvent: jest.fn() }; + const multipleApis = MultipleAnalyticsApi.fromApis([ + analyticsApiOne, + analyticsApiTwo, + ]); + + const event = { + action: 'navivate', + subject: '/path', + context: { + extension: 'App', + pluginId: 'plugin', + routeRef: 'unknown', + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('forwards events to all apis', () => { + // When an event is captured + multipleApis.captureEvent(event); + + // Then both underlying APIs should have received the event + expect(analyticsApiOne.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiOne.captureEvent).toHaveBeenCalledWith(event); + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledWith(event); + }); + + it('forwards events to all apis even if one throws an error', () => { + // Given one underlying API that throws on capture + analyticsApiOne.captureEvent.mockImplementation(() => { + throw new Error('!!!'); + }); + + // When an event is captured + multipleApis.captureEvent(event); + + // Then the other underlying API should have still received the event + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledWith(event); + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts new file mode 100644 index 0000000000..b99607a5fb --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyicsApi, + AnalyticsEvent as NewAnalyicsEvent, +} from '../../definitions'; + +/** + * An implementation of the AnalyticsApi that can be used to forward analytics + * events to multiple concrete implementations. + * + * @public + * + * @example + * + * ```jsx + * createApiFactory({ + * api: analyticsApiRef, + * deps: { configApi: configApiRef, identityApi: identityApiRef, storageApi: storageApiRef }, + * factory: ({ configApi, identityApi, storageApi }) => + * MultipleAnalyticsApi.fromApis([ + * VendorAnalyticsApi.fromConfig(configApi, { identityApi }), + * CustomAnalyticsApi.fromConfig(configApi, { identityApi, storageApi }), + * ]), + * }); + * ``` + */ +export class MultipleAnalyticsApi implements AnalyticsApi, NewAnalyicsApi { + private constructor( + private readonly actualApis: (AnalyticsApi | NewAnalyicsApi)[], + ) {} + + /** + * Create an AnalyticsApi implementation from an array of concrete + * implementations. + * + * @example + * + * ```jsx + * MultipleAnalyticsApi.fromApis([ + * SomeAnalyticsApi.fromConfig(configApi), + * new CustomAnalyticsApi(), + * ]); + * ``` + */ + static fromApis(actualApis: (AnalyticsApi | NewAnalyicsApi)[]) { + return new MultipleAnalyticsApi(actualApis); + } + + /** + * Forward the event to all configured analytics API implementations. + */ + captureEvent(event: AnalyticsEvent | NewAnalyicsEvent): void { + this.actualApis.forEach(analyticsApi => { + try { + analyticsApi.captureEvent(event as AnalyticsEvent & NewAnalyicsEvent); + } catch { + /* ignored */ + } + }); + } +} diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts new file mode 100644 index 0000000000..b64c31afaf --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyicsApi, + AnalyticsEvent as NewAnalyicsEvent, +} from '../../definitions'; + +/** + * Base implementation for the AnalyticsApi that does nothing. + * + * @public + */ +export class NoOpAnalyticsApi implements AnalyticsApi, NewAnalyicsApi { + captureEvent(_event: AnalyticsEvent | NewAnalyicsEvent): void {} +} diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts new file mode 100644 index 0000000000..c9e2008516 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { MultipleAnalyticsApi } from './MultipleAnalyticsApi'; +export { NoOpAnalyticsApi } from './NoOpAnalyticsApi'; diff --git a/packages/frontend-plugin-api/src/apis/implementations/index.ts b/packages/frontend-plugin-api/src/apis/implementations/index.ts new file mode 100644 index 0000000000..b3ec1c8513 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +// This folder contains implementations for all core APIs. +// Plugins should rely on these APIs for functionality as much as possible. + +export * from './AnalyticsApi'; diff --git a/packages/frontend-plugin-api/src/apis/index.ts b/packages/frontend-plugin-api/src/apis/index.ts index 5def6cb0f9..983497271f 100644 --- a/packages/frontend-plugin-api/src/apis/index.ts +++ b/packages/frontend-plugin-api/src/apis/index.ts @@ -15,4 +15,5 @@ */ export * from './definitions'; +export * from './implementations'; export * from './system'; From 4bc1ca5522726264a238d1fbd8fd7d5098b4251c Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 13:11:39 +0100 Subject: [PATCH 112/114] refactor: move core analytics apis to the compat package Signed-off-by: Camila Belo --- .changeset/blue-keys-do.md | 2 +- .changeset/gorgeous-pumas-draw.md | 1 + packages/core-compat-api/api-report.md | 18 ++++++++++++++++++ .../AnalyticsApi/MultipleAnalyticsApi.test.ts | 0 .../AnalyticsApi/MultipleAnalyticsApi.ts | 2 +- .../AnalyticsApi/NoOpAnalyticsApi.ts | 2 +- .../apis/implementations/AnalyticsApi/index.ts | 0 .../src/apis/implementations/index.ts | 5 +---- packages/core-compat-api/src/apis/index.ts | 17 +++++++++++++++++ packages/core-compat-api/src/index.ts | 2 ++ packages/frontend-plugin-api/api-report.md | 16 ---------------- packages/frontend-plugin-api/src/apis/index.ts | 1 - 12 files changed, 42 insertions(+), 24 deletions(-) rename packages/{frontend-plugin-api => core-compat-api}/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts (100%) rename packages/{frontend-plugin-api => core-compat-api}/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts (98%) rename packages/{frontend-plugin-api => core-compat-api}/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts (96%) rename packages/{frontend-plugin-api => core-compat-api}/src/apis/implementations/AnalyticsApi/index.ts (100%) rename packages/{frontend-plugin-api => core-compat-api}/src/apis/implementations/index.ts (77%) create mode 100644 packages/core-compat-api/src/apis/index.ts diff --git a/.changeset/blue-keys-do.md b/.changeset/blue-keys-do.md index 81e79a7975..c933bac0f2 100644 --- a/.changeset/blue-keys-do.md +++ b/.changeset/blue-keys-do.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': minor --- -**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root` and update the `MultipleAnalyticsApi` to support new Analytics API context type . +**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root`. diff --git a/.changeset/gorgeous-pumas-draw.md b/.changeset/gorgeous-pumas-draw.md index c5b2214829..2e94c61c63 100644 --- a/.changeset/gorgeous-pumas-draw.md +++ b/.changeset/gorgeous-pumas-draw.md @@ -2,6 +2,7 @@ '@backstage/plugin-analytics-module-newrelic-browser': minor '@backstage/plugin-analytics-module-ga4': minor '@backstage/plugin-analytics-module-ga': minor +'@backstage/core-compat-api': minor --- Add support to the new analytics api. diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index a7aae2b5b9..be80ca50e9 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -3,6 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnalyticsApi } from '@backstage/core-plugin-api'; +import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; +import { AnalyticsEvent } from '@backstage/core-plugin-api'; +import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; @@ -51,6 +55,20 @@ export function convertLegacyRouteRefs< [KName in keyof TRefs]: ToNewRouteRef; }; +// @public +export class MultipleAnalyticsApi implements AnalyticsApi, AnalyticsApi_2 { + captureEvent(event: AnalyticsEvent | AnalyticsEvent_2): void; + static fromApis( + actualApis: (AnalyticsApi | AnalyticsApi_2)[], + ): MultipleAnalyticsApi; +} + +// @public +export class NoOpAnalyticsApi implements AnalyticsApi, AnalyticsApi_2 { + // (undocumented) + captureEvent(_event: AnalyticsEvent | AnalyticsEvent_2): void; +} + // @public export type ToNewRouteRef = T extends RouteRef diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts b/packages/core-compat-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts similarity index 100% rename from packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts rename to packages/core-compat-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts b/packages/core-compat-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts similarity index 98% rename from packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts rename to packages/core-compat-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts index b99607a5fb..888474877d 100644 --- a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts +++ b/packages/core-compat-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts @@ -17,7 +17,7 @@ import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; import { AnalyticsApi as NewAnalyicsApi, AnalyticsEvent as NewAnalyicsEvent, -} from '../../definitions'; +} from '@backstage/frontend-plugin-api'; /** * An implementation of the AnalyticsApi that can be used to forward analytics diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts b/packages/core-compat-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts similarity index 96% rename from packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts rename to packages/core-compat-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts index b64c31afaf..ad57258fdd 100644 --- a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts +++ b/packages/core-compat-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts @@ -18,7 +18,7 @@ import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; import { AnalyticsApi as NewAnalyicsApi, AnalyticsEvent as NewAnalyicsEvent, -} from '../../definitions'; +} from '@backstage/frontend-plugin-api'; /** * Base implementation for the AnalyticsApi that does nothing. diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts b/packages/core-compat-api/src/apis/implementations/AnalyticsApi/index.ts similarity index 100% rename from packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts rename to packages/core-compat-api/src/apis/implementations/AnalyticsApi/index.ts diff --git a/packages/frontend-plugin-api/src/apis/implementations/index.ts b/packages/core-compat-api/src/apis/implementations/index.ts similarity index 77% rename from packages/frontend-plugin-api/src/apis/implementations/index.ts rename to packages/core-compat-api/src/apis/implementations/index.ts index b3ec1c8513..82fc567355 100644 --- a/packages/frontend-plugin-api/src/apis/implementations/index.ts +++ b/packages/core-compat-api/src/apis/implementations/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,4 @@ * limitations under the License. */ -// This folder contains implementations for all core APIs. -// Plugins should rely on these APIs for functionality as much as possible. - export * from './AnalyticsApi'; diff --git a/packages/core-compat-api/src/apis/index.ts b/packages/core-compat-api/src/apis/index.ts new file mode 100644 index 0000000000..dd4cc2a930 --- /dev/null +++ b/packages/core-compat-api/src/apis/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './implementations'; diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index b632bf8a57..88e1892eac 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -15,6 +15,8 @@ */ export * from './compatWrapper'; +export * from './apis'; + export { convertLegacyApp } from './convertLegacyApp'; export { convertLegacyRouteRef, diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 21d329fae8..4116469c2a 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -8,8 +8,6 @@ import { AlertApi } from '@backstage/core-plugin-api'; import { alertApiRef } from '@backstage/core-plugin-api'; import { AlertMessage } from '@backstage/core-plugin-api'; -import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/core-plugin-api'; -import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/core-plugin-api'; import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; import { ApiFactory } from '@backstage/core-plugin-api'; @@ -995,20 +993,6 @@ export { identityApiRef }; export { microsoftAuthApiRef }; -// @public -export class MultipleAnalyticsApi implements AnalyticsApi_2, AnalyticsApi { - captureEvent(event: AnalyticsEvent_2 | AnalyticsEvent): void; - static fromApis( - actualApis: (AnalyticsApi_2 | AnalyticsApi)[], - ): MultipleAnalyticsApi; -} - -// @public -export class NoOpAnalyticsApi implements AnalyticsApi_2, AnalyticsApi { - // (undocumented) - captureEvent(_event: AnalyticsEvent_2 | AnalyticsEvent): void; -} - export { OAuthApi }; export { OAuthRequestApi }; diff --git a/packages/frontend-plugin-api/src/apis/index.ts b/packages/frontend-plugin-api/src/apis/index.ts index 983497271f..5def6cb0f9 100644 --- a/packages/frontend-plugin-api/src/apis/index.ts +++ b/packages/frontend-plugin-api/src/apis/index.ts @@ -15,5 +15,4 @@ */ export * from './definitions'; -export * from './implementations'; export * from './system'; From d8f73229d63533699e88b90d8b0527a2b49fe1bc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 30 Jan 2024 13:48:44 +0100 Subject: [PATCH 113/114] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- beps/0003-auth-architecture-evolution/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beps/0003-auth-architecture-evolution/README.md b/beps/0003-auth-architecture-evolution/README.md index f70f19e8f9..ba9ffd5ae4 100644 --- a/beps/0003-auth-architecture-evolution/README.md +++ b/beps/0003-auth-architecture-evolution/README.md @@ -30,15 +30,15 @@ creation-date: 2024-01-28 This proposal outlines a new architecture for authenticating users and services in Backstage. It adds built-in access restriction to Backstage instances that protects them from outside access, and enables more granular access control for inter-plugin communication and access from external services. -It proposes a new `AuthService` interface that handles all uses and service authentication and token management available to plugins, as well as a new `HttpAuthService` interface that is a higher level service used to protect endpoints of plugin routers. The `auth-backend` will also be extended to support issuing of user tokens with a reduced scope for cookie-based authentication of requests. +It proposes a new `AuthService` interface that handles all user and service authentication and token management available to plugins, as well as a new `HttpAuthService` interface that is a higher level service used to protect endpoints of plugin routers. The `auth-backend` will also be extended to support issuing of user tokens with a reduced scope for cookie-based authentication of requests. -The changes to the service-to-service auth are aimed to be the minimal needed to get the necessary interfaces in place, and will rely on the existing symmetrical keys for now. +The changes to the service-to-service auth are aimed to be the minimum needed to get the necessary interfaces in place, and will rely on the existing symmetrical keys for now. ## Motivation This proposal aims to address several of the points in the [Auth Meta issue](https://github.com/backstage/backstage/issues/15999), with the overarching goal being to replace the existing [API request authentication](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial in `contrib/` with a more robust and secure built-in solution. The tutorial exists for two purposes: to add authentication of API requests as part of using the permission system in Backstage, and to protect a Backstage instance from external access. It does a fairly good job of the former, although we want to avoid placing user tokens in cookies, but it does a quite poor job of the latter, which we want to fix. -A secondary goal is to do this work before stabilizing the APIs in the new Backend system, as it will have some impact on how plugin backends are built. This will inevitably also lead to the need to improve the way that service-to-service auth it handled in Backstage, although that is not the primary goal of this work. +A secondary goal is to do this work before stabilizing the APIs in the new Backend system, as it will have some impact on how plugin backends are built. This will inevitably also lead to the need to improve the way that service-to-service auth is handled in Backstage, although that is not the primary goal of this work. By providing protection of Backstage instances out of the box we drastically reduce both the barrier of entry as well as security risks for Backstage adopters. It will no longer be a requirement to either set up protection of your Backstage instance or not do so and risk exposing your instance to malicious actors. It also drastically reduces the complexity of enabling the permission system of Backstage, since access restrictions are already built-in. @@ -62,11 +62,11 @@ The following goals are the primary focus of this BEP: Two new backend service interfaces are introduced to support these new features. The new `AuthService` is a low-level service that encapsulates authentication and creation of bearer tokens for all types of Backstage identities, including user, service, and services making requests on behalf of users. The new `HttpAuthService` is a higher level service that lets your create [express middleware](https://expressjs.com/en/guide/using-middleware.html) to protect endpoints, as well as access the credentials and identity of incoming requests. These new services replace the existing `IdentityService` and `TokenManagerService` -The proposed design leaves the decision for how different endpoints are protected to the implementation of the plugin backends themselves. This includes whether particular routes should allow anonymous access, access from users authenticated via a cookie, or perhaps only allow access from other plugin backends and external services. This means that integrators to not need to and do not have the ability to configure access controls of individual endpoints, except for what the permission system already provides, and what is made available through static configuration or extension points. +The proposed design leaves the decision for how different endpoints are protected to the implementation of the plugin backends themselves. This includes whether particular routes should allow anonymous access, access from users authenticated via a cookie, or perhaps only allow access from other plugin backends and external services. This means that integrators do not need to - and do not have the ability to - configure access controls of individual endpoints, except for what the permission system already provides, and what is made available through static configuration or extension points. -In order to allow for cookie-based authentication of incoming users requests, the `auth` plugin backend is extended to be able to issue users tokens with reduced scope, which in turn integrate with the new `AuthService` and `HttpAuthService`. The ability to use cookie auth for request is opt-in per route and is only be permitted for read methods (`GET`, `HEAD`, `OPTIONS`). The actual implementation of cookie-based flows will be up to each plugin, but with significant help from the new auth service interfaces. +In order to allow for cookie-based authentication of incoming user requests, the `auth` plugin backend is extended to be able to issue user tokens with reduced scope, which in turn integrate with the new `AuthService` and `HttpAuthService`. The ability to use cookie auth for requests is an opt-in per route and is only be permitted for read methods (`GET`, `HEAD`, `OPTIONS`). The actual implementation of cookie-based flows will be up to each plugin, but with significant help from the new auth service interfaces. -For service-to-service communication we will move away from reusing user tokens in upstream requests. We will instead implement an "On-Behalf-Of" flow where incoming user tokens are encapsulated in a service credential for the upstream request. In line with this the new auth service interfaces will aim to make it difficult to directly forward credentials from incoming requests, and instead encourage that plugin backends issue new service credentials for upstream requests. +For service-to-service communication we will move away from reusing user tokens in upstream requests. We will instead implement an "On-Behalf-Of" flow where incoming user credentials are encapsulated in a service token for the upstream request. In line with this the new auth service interfaces will aim to make it difficult to directly forward credentials from incoming requests, and instead encourage that plugin backends issue new service credentials for upstream requests. ## Design Details From daa64dcfabcf4f8e2efccf05fcaf6f6ee68601fe Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 30 Jan 2024 13:50:15 +0100 Subject: [PATCH 114/114] beps/0003: add goal to implement some obo flow Signed-off-by: Patrik Oldsberg --- beps/0003-auth-architecture-evolution/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/beps/0003-auth-architecture-evolution/README.md b/beps/0003-auth-architecture-evolution/README.md index ba9ffd5ae4..cc076acae1 100644 --- a/beps/0003-auth-architecture-evolution/README.md +++ b/beps/0003-auth-architecture-evolution/README.md @@ -52,6 +52,7 @@ The following goals are the primary focus of this BEP: - Cookie-based authentication of requests for static assets that protects against CSRF attacks and does not unnecessarily expose user tokens. - Basic improvements to the service-to-service auth service interfaces such that we are confident that we do not need to break them in the near future. - If possible we will keep using the existing symmetrical keys that are used today, but it is likely that we will need to break compatibility of existing tokens. + - Encapsulation of user credentials in upstream service requests, avoiding the pattern where backend plugins re-use the user token directly for their outgoing requests. ### Non-Goals