From a5d73da942a3f33584326068b453f737dae0dd12 Mon Sep 17 00:00:00 2001 From: Mengnan Gong Date: Fri, 3 Jun 2022 14:26:51 +0800 Subject: [PATCH 01/73] techdocs-cli: fix the legacyCopyReadmeMdToIndexMd flag in techdocs-cli generate Signed-off-by: Mengnan Gong --- .changeset/happy-boxes-melt.md | 6 ++++++ packages/techdocs-cli/src/commands/generate/generate.ts | 2 +- plugins/techdocs-node/src/stages/generate/techdocs.ts | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .changeset/happy-boxes-melt.md diff --git a/.changeset/happy-boxes-melt.md b/.changeset/happy-boxes-melt.md new file mode 100644 index 0000000000..c478e783d3 --- /dev/null +++ b/.changeset/happy-boxes-melt.md @@ -0,0 +1,6 @@ +--- +'@techdocs/cli': patch +'@backstage/plugin-techdocs-node': patch +--- + +Fix the flag parsing for `legacyCopyReadmeMdToIndexMd` in `techdocs-cli generate` command, and decouple it's logic from the `techdocs-ref` flag. diff --git a/packages/techdocs-cli/src/commands/generate/generate.ts b/packages/techdocs-cli/src/commands/generate/generate.ts index 309b9c7969..1e4ed220d9 100644 --- a/packages/techdocs-cli/src/commands/generate/generate.ts +++ b/packages/techdocs-cli/src/commands/generate/generate.ts @@ -57,8 +57,8 @@ export default async function generate(opts: OptionValues) { runIn: opts.docker ? 'docker' : 'local', dockerImage, pullImage, - legacyCopyReadmeMdToIndexMd, mkdocs: { + legacyCopyReadmeMdToIndexMd, omitTechdocsCorePlugin, }, }, diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index 660b8e8cb5..e3debab526 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -111,10 +111,10 @@ export class TechdocsGenerator implements GeneratorBase { parsedLocationAnnotation, this.scmIntegrations, ); + } - if (this.options.legacyCopyReadmeMdToIndexMd) { - await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir }); - } + if (this.options.legacyCopyReadmeMdToIndexMd) { + await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir }); } if (!this.options.omitTechdocsCoreMkdocsPlugin) { From 389f6602bcb4e988366e105725f5d616bbdb2a69 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 10 Jun 2022 17:38:19 +0400 Subject: [PATCH 02/73] added new getDags method to fetch specific dagIds Signed-off-by: Daniele.Mazzotta --- .../src/api/ApacheAirflowApi.ts | 1 + .../src/api/ApacheAirflowClient.test.ts | 27 +++++++++++++++++++ .../src/api/ApacheAirflowClient.ts | 19 +++++++++++++ .../DagTableComponent/DagTableComponent.tsx | 17 +++++++++++- 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/plugins/apache-airflow/src/api/ApacheAirflowApi.ts b/plugins/apache-airflow/src/api/ApacheAirflowApi.ts index dde8c57802..838292524a 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowApi.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowApi.ts @@ -25,6 +25,7 @@ export type ApacheAirflowApi = { discoveryApi: DiscoveryApi; baseUrl: string; listDags(options?: { objectsPerRequest: number }): Promise; + getDags(dagIds: string[]): Promise<{ dags: Dag[]; dagsNotFound: string[] }>; updateDag(dagId: string, isPaused: boolean): Promise; getInstanceStatus(): Promise; getInstanceVersion(): Promise; diff --git a/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts b/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts index 704b17eb34..e56c622ff2 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts @@ -160,4 +160,31 @@ describe('ApacheAirflowClient', () => { expect(response.dag_id).toEqual(dagId); expect(response.is_paused).toEqual(true); }); + + it('get only some dags', async () => { + setupHandlers(); + const client = new ApacheAirflowClient({ + discoveryApi: discoveryApi, + baseUrl: 'localhost:8080/', + }); + const dagIds = ['mock_dag_1', 'mock_dag_3']; + const response = await client.getDags(dagIds); + expect(response.dags.length).toEqual(dagIds.length); + response.dags.forEach((dag, index) => + expect(dag.dag_id).toEqual(dagIds[index]), + ); + expect(response.dagsNotFound.length).toEqual(0); + }); + + it('get dags but ignore NOT FOUND errors', async () => { + setupHandlers(); + const client = new ApacheAirflowClient({ + discoveryApi: discoveryApi, + baseUrl: 'localhost:8080/', + }); + const dagIds = ['mock_dag_1', 'a-random-DAG-id']; + const response = await client.getDags(dagIds); + expect(response.dags[0].dag_id).toEqual('mock_dag_1'); + expect(response.dagsNotFound[0]).toEqual('a-random-DAG-id'); + }); }); diff --git a/plugins/apache-airflow/src/api/ApacheAirflowClient.ts b/plugins/apache-airflow/src/api/ApacheAirflowClient.ts index 847a1b1ca6..bf095a96ba 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowClient.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowClient.ts @@ -75,6 +75,25 @@ export class ApacheAirflowClient implements ApacheAirflowApi { return dags; } + async getDags( + dagIds: string[], + ): Promise<{ dags: Dag[]; dagsNotFound: string[] }> { + const dagsNotFound: string[] = []; + const response = await Promise.all( + dagIds.map(id => { + return this.fetch(`/dags/${id}`).catch(e => { + if (e.message === 'NOT FOUND') { + dagsNotFound.push(id); + } else { + throw e; + } + }); + }), + ); + const dags = response.filter(Boolean) as Dag[]; + return { dags, dagsNotFound }; + } + async updateDag(dagId: string, isPaused: boolean): Promise { const init = { method: 'PATCH', diff --git a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx index cf343a596e..18439e1902 100644 --- a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx +++ b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx @@ -129,9 +129,24 @@ export const DenseTable = ({ dags }: DenseTableProps) => { ); }; -export const DagTableComponent = () => { +type DagTableComponentProps = { + dagIds?: string[]; +}; + +export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { const apiClient = useApi(apacheAirflowApiRef); const { value, loading, error } = useAsync(async (): Promise => { + if (dagIds) { + const { dags, dagsNotFound } = await apiClient.getDags(dagIds); + if (dagsNotFound.length) { + throw new Error( + `${dagsNotFound.length} DAGs were not found:\n${dagsNotFound.join( + ';\n', + )}`, + ); + } + return dags; + } return await apiClient.listDags(); }, []); From b1c5b42849a400dbac414df2d8f20c813d2111a1 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 15 Jun 2022 13:58:33 +0200 Subject: [PATCH 03/73] added type predicates for entities Signed-off-by: Alex Rybchenko --- .../src/components/EntitySwitch/conditions.ts | 61 ++++++++++++++++++- .../src/components/EntitySwitch/index.ts | 2 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/EntitySwitch/conditions.ts b/plugins/catalog/src/components/EntitySwitch/conditions.ts index 5f50fde5c9..b6837627c0 100644 --- a/plugins/catalog/src/components/EntitySwitch/conditions.ts +++ b/plugins/catalog/src/components/EntitySwitch/conditions.ts @@ -14,7 +14,17 @@ * limitations under the License. */ -import { Entity, ComponentEntity } from '@backstage/catalog-model'; +import { + ApiEntity, + ComponentEntity, + DomainEntity, + Entity, + GroupEntity, + LocationEntity, + ResourceEntity, + SystemEntity, + UserEntity, +} from '@backstage/catalog-model'; function strCmp(a: string | undefined, b: string | undefined): boolean { return Boolean( @@ -57,3 +67,52 @@ export function isComponentType(types: string | string[]) { export function isNamespace(namespaces: string | string[]) { return (entity: Entity) => strCmpAll(entity.metadata?.namespace, namespaces); } + +/** + * @public + */ +export function isApiEntity(entity: Entity): entity is ApiEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'API'; +} +/** + * @public + */ +export function isComponentEntity(entity: Entity): entity is ComponentEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'COMPONENT'; +} +/** + * @public + */ +export function isDomainEntity(entity: Entity): entity is DomainEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'DOMAIN'; +} +/** + * @public + */ +export function isGroupEntity(entity: Entity): entity is GroupEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'GROUP'; +} +/** + * @public + */ +export function isLocationEntity(entity: Entity): entity is LocationEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'LOCATION'; +} +/** + * @public + */ +export function isResourceEntity(entity: Entity): entity is ResourceEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'RESOURCE'; +} +/** + * @public + */ +export function isSystemEntity(entity: Entity): entity is SystemEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'SYSTEM'; +} +/** + * @public + */ +export function isUserEntity(entity: Entity): entity is UserEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'USER'; +} diff --git a/plugins/catalog/src/components/EntitySwitch/index.ts b/plugins/catalog/src/components/EntitySwitch/index.ts index 6cccf4be6e..5d0537bfa5 100644 --- a/plugins/catalog/src/components/EntitySwitch/index.ts +++ b/plugins/catalog/src/components/EntitySwitch/index.ts @@ -16,4 +16,4 @@ export { EntitySwitch } from './EntitySwitch'; export type { EntitySwitchProps, EntitySwitchCaseProps } from './EntitySwitch'; -export { isKind, isNamespace, isComponentType } from './conditions'; +export * from './conditions'; From 49ff472c0b527bea729ce23de65fb62f26dd20ad Mon Sep 17 00:00:00 2001 From: ivgo Date: Wed, 15 Jun 2022 15:27:30 +0200 Subject: [PATCH 04/73] Add possibility to scan all the groups in the project Signed-off-by: ivgo --- .changeset/two-crews-accept.md | 14 ++++ docs/integrations/gitlab/discovery.md | 12 +++ .../catalog-backend-module-gitlab/config.d.ts | 74 ++++++++++++------- .../src/providers/config.test.ts | 17 +++++ .../src/providers/config.ts | 17 ++++- 5 files changed, 105 insertions(+), 29 deletions(-) create mode 100644 .changeset/two-crews-accept.md diff --git a/.changeset/two-crews-accept.md b/.changeset/two-crews-accept.md new file mode 100644 index 0000000000..5885b1346d --- /dev/null +++ b/.changeset/two-crews-accept.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': minor +--- + +Add the possibility in the `GitlabDiscoveryEntityProvider` to scan the whole project instead of concrete groups. For that, use a configuration like this one: + +```yaml +catalog: + providers: + gitlab: + host: gitlab-host # Identifies one of the hosts set up in the integrations + branch: main # Optional. Uses `master` as default + entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` +``` diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index ea151e9415..a7dd0a03df 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -26,6 +26,18 @@ catalog: entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` ``` +If you desire so, it's also possible to execute the gitlab discovery in your entire +project. In order to do that, use this catalog configuration instead: + +```yaml +catalog: + providers: + gitlab: + host: gitlab-host # Identifies one of the hosts set up in the integrations + branch: main # Optional. Uses `master` as default + entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` +``` + As this provider is not one of the default providers, you will first need to install the gitlab catalog plugin: diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 5b0d42c81d..0626cb9488 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -23,33 +23,53 @@ export interface Config { /** * GitlabDiscoveryEntityProvider configuration */ - gitlab?: Record< - string, - { - /** - * (Required) Gitlab's host name. - * @visibility backend - */ - host: string; - /** - * (Required) Gitlab's group[/subgroup] where the discovery is done. - * @visibility backend - */ - group: string; - /** - * (Optional) Default branch to read the catalog-info.yaml file. - * If not set, 'master' will be used. - * @visibility backend - */ - branch?: string; - /** - * (Optional) The name used for the catalog file. - * If not set, 'catalog-info.yaml' will be used. - * @visibility backend - */ - entityFilename?: string; - } - >; + gitlab?: + | { + /** + * (Required) Gitlab's host name. + * @visibility backend + */ + host: string; + /** + * (Optional) Default branch to read the catalog-info.yaml file. + * If not set, 'master' will be used. + * @visibility backend + */ + branch?: string; + /** + * (Optional) The name used for the catalog file. + * If not set, 'catalog-info.yaml' will be used. + * @visibility backend + */ + entityFilename?: string; + } + | Record< + string, + { + /** + * (Required) Gitlab's host name. + * @visibility backend + */ + host: string; + /** + * (Required) Gitlab's group[/subgroup] where the discovery is done. + * @visibility backend + */ + group: string; + /** + * (Optional) Default branch to read the catalog-info.yaml file. + * If not set, 'master' will be used. + * @visibility backend + */ + branch?: string; + /** + * (Optional) The name used for the catalog file. + * If not set, 'catalog-info.yaml' will be used. + * @visibility backend + */ + entityFilename?: string; + } + >; }; }; } diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index d28930b151..429743f4bd 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -103,4 +103,21 @@ describe('config', () => { "Missing required config value at 'catalog.providers.gitlab.test.group'", ); }); + + it('read full gitlab project', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitlab: { + host: 'host', + branch: 'main', + }, + }, + }, + }); + + const result = readGitlabConfigs(config); + expect(result).toHaveLength(1); + expect(result[0].group).toEqual(''); + }); }); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index f7eb316997..56df8d62f1 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -48,6 +48,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { * @param config - The config object to extract from */ export function readGitlabConfigs(config: Config): GitlabProviderConfig[] { + const reservedParams = ['host', 'group', 'branch', 'catalogFile']; const configs: GitlabProviderConfig[] = []; const providerConfigs = config.getOptionalConfig('catalog.providers.gitlab'); @@ -56,8 +57,20 @@ export function readGitlabConfigs(config: Config): GitlabProviderConfig[] { return configs; } - for (const id of providerConfigs.keys()) { - configs.push(readGitlabConfig(id, providerConfigs.getConfig(id))); + if (providerConfigs.keys().some(r => reservedParams.indexOf(r) >= 0)) { + configs.push({ + id: 'full-check', + group: '', + branch: providerConfigs.getOptionalString('branch') ?? 'master', + host: providerConfigs.getString('host'), + catalogFile: + providerConfigs.getOptionalString('entityFilename') ?? + 'catalog-info.yaml', + }); + } else { + for (const id of providerConfigs.keys()) { + configs.push(readGitlabConfig(id, providerConfigs.getConfig(id))); + } } return configs; From 1e984b11fccefdffefb4544e448c6040bcd5a741 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Thu, 16 Jun 2022 15:53:48 -0400 Subject: [PATCH 05/73] refactor(MyGroupsSidebarItem): Render namespaced teams within dropdown items Signed-off-by: Phil Kuang --- .changeset/shiny-turkeys-doubt.md | 5 +++ .../MyGroupsSidebarItem.tsx | 38 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 .changeset/shiny-turkeys-doubt.md diff --git a/.changeset/shiny-turkeys-doubt.md b/.changeset/shiny-turkeys-doubt.md new file mode 100644 index 0000000000..0f663a4c74 --- /dev/null +++ b/.changeset/shiny-turkeys-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Render namespaced teams within dropdown items in `MyGroupsSidebarItem` diff --git a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx index fa8f8405ff..445bf8e6fe 100644 --- a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx +++ b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx @@ -15,7 +15,11 @@ */ import React from 'react'; -import { stringifyEntityRef } from '@backstage/catalog-model'; +import { + DEFAULT_NAMESPACE, + Entity, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { SidebarItem, SidebarSubmenu, @@ -32,7 +36,6 @@ import { catalogApiRef, CatalogApi, entityRouteRef, - humanizeEntityRef, } from '@backstage/plugin-catalog-react'; import { getCompoundEntityRef } from '@backstage/catalog-model'; @@ -87,23 +90,42 @@ export const MyGroupsSidebarItem = (props: { ); } + const namespacedGroupsMap: { [namespace: string]: Entity[] } = {}; + groups.forEach(group => { + namespacedGroupsMap[group.metadata.namespace!] = + namespacedGroupsMap[group.metadata.namespace!] ?? []; + namespacedGroupsMap[group.metadata.namespace!].push(group); + }); + // Member of more than one group return ( - {groups?.map(function groupsMap(group) { + {namespacedGroupsMap[DEFAULT_NAMESPACE]?.map(group => { return ( ); })} + {Object.entries(namespacedGroupsMap) + .filter(([n]) => n !== DEFAULT_NAMESPACE) + .map(([namespace, namespacedGroups]) => { + return ( + ({ + title: group.metadata.title || group.metadata.name, + to: catalogEntityRoute(getCompoundEntityRef(group)), + }))} + /> + ); + })} ); From 860765ff4524d68d18bf77b7b2895ea195b3c384 Mon Sep 17 00:00:00 2001 From: Niklas Aronsson Date: Mon, 25 Apr 2022 13:15:21 +0200 Subject: [PATCH 06/73] [TechDocs] Added configuration for local publishing target This patch adds a configuration option for setting the "local" techdocs target directory. The target directory can be set using the "techdocs.publisher.local.publishDirectory". This fixes two "TODOs" in the "plugins/techdocs-node/src/stages /publish/local.ts" file: * Use a more persistent storage than node_modules or /tmp directory. Make it configurable with techdocs.publisher.local.publishDirectory * Move the logic of setting staticDocsDir based on config over to fromConfig, and set the value as a class parameter. Signed-off-by: Niklas Aronsson --- .changeset/calm-experts-buy.md | 5 ++ .changeset/weak-llamas-repeat.md | 5 ++ docs/features/techdocs/configuration.md | 6 ++ plugins/techdocs-backend/config.d.ts | 7 +++ .../src/stages/publish/local.test.ts | 27 +++++++++ .../techdocs-node/src/stages/publish/local.ts | 57 +++++++++++-------- 6 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 .changeset/calm-experts-buy.md create mode 100644 .changeset/weak-llamas-repeat.md diff --git a/.changeset/calm-experts-buy.md b/.changeset/calm-experts-buy.md new file mode 100644 index 0000000000..d95844e298 --- /dev/null +++ b/.changeset/calm-experts-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': minor +--- + +Added local publishing target directory `config`: `techdocs.publisher.local.publishDirectory` diff --git a/.changeset/weak-llamas-repeat.md b/.changeset/weak-llamas-repeat.md new file mode 100644 index 0000000000..a86ce3a964 --- /dev/null +++ b/.changeset/weak-llamas-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': minor +--- + +Added local publishing target directory `config`: `techdocs.publisher.local.publishDirectory` diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index d14850da77..9440d9a824 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -74,6 +74,12 @@ techdocs: type: 'local' + # Optional when techdocs.publisher.type is set to 'local'. + + local: + # (Optional). Set this to specify where the generated documentation is stored. + publishDirectory: '/path/to/local/directory' + # Required when techdocs.publisher.type is set to 'googleGcs'. Skip otherwise. googleGcs: diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index c64a001f5f..fdacc9cfbc 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -66,6 +66,13 @@ export interface Config { publisher?: | { type: 'local'; + + local?: { + /** + * Directory to store generated static files. + */ + publishDirectory?: string; + }; } | { type: 'awsS3'; diff --git a/plugins/techdocs-node/src/stages/publish/local.test.ts b/plugins/techdocs-node/src/stages/publish/local.test.ts index 0119def16d..ce2e0c3ccd 100644 --- a/plugins/techdocs-node/src/stages/publish/local.test.ts +++ b/plugins/techdocs-node/src/stages/publish/local.test.ts @@ -236,5 +236,32 @@ describe('local publisher', () => { ); expect(response.status).toBe(404); }); + + it('should work with a configured directory', async () => { + const customConfig = new ConfigReader({ + techdocs: { + publisher: { + local: { + publishDirectory: tmpDir, + }, + }, + }, + }); + mockFs({ + [tmpDir]: { + 'index.html': 'found it', + }, + }); + const legacyPublisher = LocalPublish.fromConfig( + customConfig, + logger, + testDiscovery, + ); + app = express().use(legacyPublisher.docsRouter()); + + const response = await request(app).get('/index.html'); + expect(response.status).toBe(200); + expect(response.text).toEqual('found it'); + }); }); }); diff --git a/plugins/techdocs-node/src/stages/publish/local.ts b/plugins/techdocs-node/src/stages/publish/local.ts index 11c6e6dd2b..1a02cb7eee 100644 --- a/plugins/techdocs-node/src/stages/publish/local.ts +++ b/plugins/techdocs-node/src/stages/publish/local.ts @@ -44,40 +44,27 @@ import { } from './helpers'; import { ForwardedError } from '@backstage/errors'; -// TODO: Use a more persistent storage than node_modules or /tmp directory. -// Make it configurable with techdocs.publisher.local.publishDirectory -let staticDocsDir = ''; -try { - staticDocsDir = resolvePackagePath( - '@backstage/plugin-techdocs-backend', - 'static/docs', - ); -} catch (err) { - // This will most probably never be used. - // The try/catch is introduced so that techdocs-cli can import @backstage/plugin-techdocs-node - // on CI/CD without installing techdocs backend plugin. - staticDocsDir = os.tmpdir(); -} - /** - * Local publisher which uses the local filesystem to store the generated static files. It uses a directory - * called "static" at the root of techdocs-backend plugin. + * Local publisher which uses the local filesystem to store the generated static files. It uses by default a + * directory called "static" at the root of techdocs-backend plugin unless a directory has been configured by + * "techdocs.publisher.local.publishDirectory". */ export class LocalPublish implements PublisherBase { private readonly legacyPathCasing: boolean; private readonly logger: Logger; private readonly discovery: PluginEndpointDiscovery; + private readonly staticDocsDir: string; - // TODO: Move the logic of setting staticDocsDir based on config over to - // fromConfig, and set the value as a class parameter. constructor(options: { logger: Logger; discovery: PluginEndpointDiscovery; legacyPathCasing: boolean; + staticDocsDir: string; }) { this.logger = options.logger; this.discovery = options.discovery; this.legacyPathCasing = options.legacyPathCasing; + this.staticDocsDir = options.staticDocsDir; } static fromConfig( @@ -90,10 +77,28 @@ export class LocalPublish implements PublisherBase { 'techdocs.legacyUseCaseSensitiveTripletPaths', ) || false; + let staticDocsDir = config.getOptionalString( + 'techdocs.publisher.local.publishDirectory', + ); + if (!staticDocsDir) { + try { + staticDocsDir = resolvePackagePath( + '@backstage/plugin-techdocs-backend', + 'static/docs', + ); + } catch (err) { + // This will most probably never be used. + // The try/catch is introduced so that techdocs-cli can import @backstage/plugin-techdocs-node + // on CI/CD without installing techdocs backend plugin. + staticDocsDir = os.tmpdir(); + } + } + return new LocalPublish({ logger, discovery, legacyPathCasing, + staticDocsDir, }); } @@ -144,7 +149,7 @@ export class LocalPublish implements PublisherBase { const techdocsApiUrl = await this.discovery.getBaseUrl('techdocs'); const publishedFilePaths = (await getFileTreeRecursively(publishDir)).map( abs => { - return abs.split(`${staticDocsDir}/`)[1]; + return abs.split(`${this.staticDocsDir}/`)[1]; }, ); @@ -222,9 +227,8 @@ export class LocalPublish implements PublisherBase { // Otherwise, redirect to the new path. return res.redirect(301, req.baseUrl + newPath); }); - router.use( - express.static(staticDocsDir, { + express.static(this.staticDocsDir, { // Handle content-type header the same as all other publishers. setHeaders: (res, filePath) => { const fileExtension = path.extname(filePath); @@ -275,13 +279,16 @@ export class LocalPublish implements PublisherBase { concurrency = 25, }): Promise { // Iterate through every file in the root of the publisher. - const files = await getFileTreeRecursively(staticDocsDir); + const files = await getFileTreeRecursively(this.staticDocsDir); const limit = createLimiter(concurrency); await Promise.all( files.map(f => limit(async file => { - const relativeFile = file.replace(`${staticDocsDir}${path.sep}`, ''); + const relativeFile = file.replace( + `${this.staticDocsDir}${path.sep}`, + '', + ); const newFile = lowerCaseEntityTripletInStoragePath(relativeFile); // If all parts are already lowercase, ignore. @@ -311,7 +318,7 @@ export class LocalPublish implements PublisherBase { * Utility wrapper around path.join(), used to control legacy case logic. */ protected staticEntityPathJoin(...allParts: string[]): string { - let staticEntityPath = staticDocsDir; + let staticEntityPath = this.staticDocsDir; allParts .map(part => part.split(path.sep)) From c05f081e61b5f02b77fc0e98838d96c2ddc51f7e Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 12:11:47 +0400 Subject: [PATCH 07/73] test for dags found and not found length Signed-off-by: Daniele.Mazzotta --- plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts b/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts index e56c622ff2..329f89ee52 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts @@ -184,7 +184,9 @@ describe('ApacheAirflowClient', () => { }); const dagIds = ['mock_dag_1', 'a-random-DAG-id']; const response = await client.getDags(dagIds); + expect(response.dags.length).toEqual(1); expect(response.dags[0].dag_id).toEqual('mock_dag_1'); + expect(response.dagsNotFound.length).toEqual(1); expect(response.dagsNotFound[0]).toEqual('a-random-DAG-id'); }); }); From 95d2f8831376c6e8318e0e5a870622112a820583 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 12:42:30 +0400 Subject: [PATCH 08/73] add warning for missing dag ids Signed-off-by: Daniele.Mazzotta --- .../DagTableComponent/DagTableComponent.tsx | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx index 18439e1902..7619b06497 100644 --- a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx +++ b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx @@ -20,6 +20,7 @@ import { StatusOK, Table, TableColumn, + WarningPanel, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import Box from '@material-ui/core/Box'; @@ -30,7 +31,7 @@ import Tooltip from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; import Alert from '@material-ui/lab/Alert'; -import React from 'react'; +import React, { useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { apacheAirflowApiRef } from '../../api'; import { Dag } from '../../api/types'; @@ -135,15 +136,14 @@ type DagTableComponentProps = { export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { const apiClient = useApi(apacheAirflowApiRef); + const [dagsNotFound, setDagsNotFound] = useState(); + const { value, loading, error } = useAsync(async (): Promise => { if (dagIds) { + // eslint-disable-next-line @typescript-eslint/no-shadow const { dags, dagsNotFound } = await apiClient.getDags(dagIds); if (dagsNotFound.length) { - throw new Error( - `${dagsNotFound.length} DAGs were not found:\n${dagsNotFound.join( - ';\n', - )}`, - ); + setDagsNotFound(dagsNotFound); } return dags; } @@ -162,5 +162,18 @@ export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { dagUrl: `${apiClient.baseUrl}dag_details?dag_id=${el.dag_id}`, // construct path to DAG using `baseUrl` })); - return ; + return ( + <> + {dagsNotFound ? ( + + {dagsNotFound.map(dagId => ( + {dagId} + ))} + + ) : ( + '' + )} + + + ); }; From 6b8093497f3a2e9a677f1f476813ca730fa7993a Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 12:43:28 +0400 Subject: [PATCH 09/73] written tests for all dags + selected dags Signed-off-by: Daniele.Mazzotta --- .../DagTableComponent.test.tsx | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.test.tsx diff --git a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.test.tsx b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.test.tsx new file mode 100644 index 0000000000..16ff3120be --- /dev/null +++ b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.test.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ApacheAirflowApi, apacheAirflowApiRef } from '../../api'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import React from 'react'; +import { DagTableComponent } from './DagTableComponent'; + +describe('DagTableComponent', () => { + const mockApi: jest.Mocked = { + listDags: jest.fn().mockResolvedValue([ + { + dag_id: 'mock_dag_1', + }, + { + dag_id: 'mock_dag_2', + }, + { + dag_id: 'mock_dag_3', + }, + ]), + getDags: jest.fn().mockResolvedValue({ + dags: [{ dag_id: 'mock_dag_1' }], + dagsNotFound: ['a-random-id'], + }), + } as any; + + it('should render all DAGs', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + ['mock_dag_1', 'mock_dag_2', 'mock_dag_3'].forEach(dagId => { + expect(getByText(dagId)).toBeInTheDocument(); + }); + }); + + it('should render only selected DAGs', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + expect(getByText('mock_dag_1')).toBeInTheDocument(); + expect(getByText('Warning: 1 DAGs were not found')).toBeInTheDocument(); + expect(getByText('a-random-id')).toBeInTheDocument(); + }); +}); From fdffb40c70b3cce481e16be2abc66fd064269b87 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 12:47:33 +0400 Subject: [PATCH 10/73] export DagTableComponent Signed-off-by: Daniele.Mazzotta --- plugins/apache-airflow/src/index.ts | 6 +++++- plugins/apache-airflow/src/plugin.ts | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/plugins/apache-airflow/src/index.ts b/plugins/apache-airflow/src/index.ts index 7186839c0a..e8353c7760 100644 --- a/plugins/apache-airflow/src/index.ts +++ b/plugins/apache-airflow/src/index.ts @@ -20,4 +20,8 @@ * @packageDocumentation */ -export { apacheAirflowPlugin, ApacheAirflowPage } from './plugin'; +export { + apacheAirflowPlugin, + ApacheAirflowPage, + ApacheAirflowDagTable, +} from './plugin'; diff --git a/plugins/apache-airflow/src/plugin.ts b/plugins/apache-airflow/src/plugin.ts index 04ef1974d4..f7e25c7003 100644 --- a/plugins/apache-airflow/src/plugin.ts +++ b/plugins/apache-airflow/src/plugin.ts @@ -17,11 +17,12 @@ import { rootRouteRef } from './routes'; import { apacheAirflowApiRef, ApacheAirflowClient } from './api'; import { + configApiRef, createApiFactory, + createComponentExtension, createPlugin, createRoutableExtension, discoveryApiRef, - configApiRef, } from '@backstage/core-plugin-api'; export const apacheAirflowPlugin = createPlugin({ @@ -49,3 +50,13 @@ export const ApacheAirflowPage = apacheAirflowPlugin.provide( mountPoint: rootRouteRef, }), ); + +export const ApacheAirflowDagTable = apacheAirflowPlugin.provide( + createComponentExtension({ + name: 'ApacheAirflowDagTable', + component: { + lazy: () => + import('./components/DagTableComponent').then(m => m.DagTableComponent), + }, + }), +); From 01f976ea728a4fe40f9d5c7a387506e09120e32b Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 17:06:56 +0400 Subject: [PATCH 11/73] added changeset Signed-off-by: Daniele.Mazzotta --- .changeset/lemon-goats-obey.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lemon-goats-obey.md diff --git a/.changeset/lemon-goats-obey.md b/.changeset/lemon-goats-obey.md new file mode 100644 index 0000000000..2c45c67282 --- /dev/null +++ b/.changeset/lemon-goats-obey.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-apache-airflow': patch +--- + +Exposed DagTableComponent as standalone component + added a prop to get only select DAGs instead of the full list From 221fbffe4464bb272b925a175538362beebd5a57 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 17:16:28 +0400 Subject: [PATCH 12/73] updated README Signed-off-by: Daniele.Mazzotta --- plugins/apache-airflow/README.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/apache-airflow/README.md b/plugins/apache-airflow/README.md index bdbb1b981f..11fce89e4c 100644 --- a/plugins/apache-airflow/README.md +++ b/plugins/apache-airflow/README.md @@ -3,14 +3,17 @@ Welcome to the apache-airflow plugin! This plugin serves as frontend to the REST API exposed by Apache Airflow. -Note only [Airflow v2 (and later)](https://airflow.apache.org/docs/apache-airflow/stable/deprecated-rest-api-ref.html) integrate with the plugin. +Note only [Airflow v2 (and later)](https://airflow.apache.org/docs/apache-airflow/stable/deprecated-rest-api-ref.html) +integrate with the plugin. ## Feature Requests & Ideas - [ ] Add support for running multiple instances of Airflow for monitoring - various deployment stages or business domains. ([Suggested by @JGoldman110](https://github.com/backstage/backstage/issues/735#issuecomment-985063468)) + various deployment stages or business + domains. ([Suggested by @JGoldman110](https://github.com/backstage/backstage/issues/735#issuecomment-985063468)) - [ ] Make owner chips in the DAG table clickable, resolving to a user or group - in the entity catalog. ([Suggested by @julioz](https://github.com/backstage/backstage/pull/8348#discussion_r764766295)) + in the entity + catalog. ([Suggested by @julioz](https://github.com/backstage/backstage/pull/8348#discussion_r764766295)) ## Installation @@ -42,6 +45,27 @@ yarn --cwd packages/app add @backstage/plugin-apache-airflow ); ``` +If you just want to embed the DAGs into an existing page, you can use the `ApacheAirflowDagTable` + +```typescript +import {ApacheAirflowDagTable} from '@backstage/plugin-apache-airflow'; + +export function SomeEntityPage(): JSX.Element { + return ( + + + < /Grid> +) + ; +} +``` + ## Configuration For links to the Airflow instance, the `baseUrl` must be defined in From d9ebda237e8020fe641361d635c348f8d43a4262 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Fri, 17 Jun 2022 17:50:27 +0400 Subject: [PATCH 13/73] build api-reports Signed-off-by: Daniele.Mazzotta --- plugins/apache-airflow/api-report.md | 11 +++++++++-- plugins/apache-airflow/src/plugin.ts | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/apache-airflow/api-report.md b/plugins/apache-airflow/api-report.md index ca151f0734..09123de613 100644 --- a/plugins/apache-airflow/api-report.md +++ b/plugins/apache-airflow/api-report.md @@ -5,8 +5,15 @@ ```ts /// -import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { RouteRef } from '@backstage/core-plugin-api'; +import {BackstagePlugin} from '@backstage/core-plugin-api'; +import {RouteRef} from '@backstage/core-plugin-api'; + +// @public +export const ApacheAirflowDagTable: ({ + dagIds, + }: { + dagIds?: string[] | undefined; +}) => JSX.Element; // Warning: (ae-missing-release-tag) "ApacheAirflowPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/apache-airflow/src/plugin.ts b/plugins/apache-airflow/src/plugin.ts index f7e25c7003..adf5879196 100644 --- a/plugins/apache-airflow/src/plugin.ts +++ b/plugins/apache-airflow/src/plugin.ts @@ -51,6 +51,13 @@ export const ApacheAirflowPage = apacheAirflowPlugin.provide( }), ); +/** + * Render the DAGs in a table + * If the dagIds is specified, only those DAGs are loaded. + * Otherwise, it's going to list all the DAGs + * @public + * @param dagIds - string[] + */ export const ApacheAirflowDagTable = apacheAirflowPlugin.provide( createComponentExtension({ name: 'ApacheAirflowDagTable', From 8b8c692be318d4b7468ca8f2b2867a28a15ade5c Mon Sep 17 00:00:00 2001 From: Daniele Mazzotta Date: Mon, 20 Jun 2022 12:58:38 +0400 Subject: [PATCH 14/73] updated api-reports Signed-off-by: Daniele Mazzotta --- plugins/apache-airflow/src/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/apache-airflow/src/plugin.ts b/plugins/apache-airflow/src/plugin.ts index adf5879196..664cde36cc 100644 --- a/plugins/apache-airflow/src/plugin.ts +++ b/plugins/apache-airflow/src/plugin.ts @@ -56,7 +56,7 @@ export const ApacheAirflowPage = apacheAirflowPlugin.provide( * If the dagIds is specified, only those DAGs are loaded. * Otherwise, it's going to list all the DAGs * @public - * @param dagIds - string[] + * @param dagIds - optional string[] of the DAGs to show in the table. If undefined, it will list all DAGs */ export const ApacheAirflowDagTable = apacheAirflowPlugin.provide( createComponentExtension({ From 1189f00d23d12c4728bfe4eaa39845af14b461cc Mon Sep 17 00:00:00 2001 From: ivgo Date: Mon, 20 Jun 2022 11:48:58 +0200 Subject: [PATCH 15/73] Set group as optional parameter. Scan everything if not present Signed-off-by: ivgo --- .changeset/two-crews-accept.md | 9 ++- docs/integrations/gitlab/discovery.md | 14 +--- .../catalog-backend-module-gitlab/config.d.ts | 75 +++++++------------ .../src/providers/config.test.ts | 8 +- .../src/providers/config.ts | 19 +---- 5 files changed, 42 insertions(+), 83 deletions(-) diff --git a/.changeset/two-crews-accept.md b/.changeset/two-crews-accept.md index 5885b1346d..deacf1559d 100644 --- a/.changeset/two-crews-accept.md +++ b/.changeset/two-crews-accept.md @@ -2,13 +2,14 @@ '@backstage/plugin-catalog-backend-module-gitlab': minor --- -Add the possibility in the `GitlabDiscoveryEntityProvider` to scan the whole project instead of concrete groups. For that, use a configuration like this one: +Add the possibility in the `GitlabDiscoveryEntityProvider` to scan the whole project instead of concrete groups. For that, use a configuration like this one, where the group parameter is omitted (not mandatory anymore): ```yaml catalog: providers: gitlab: - host: gitlab-host # Identifies one of the hosts set up in the integrations - branch: main # Optional. Uses `master` as default - entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` + yourProviderId: + host: gitlab-host # Identifies one of the hosts set up in the integrations + branch: main # Optional. Uses `master` as default + entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` ``` diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index a7dd0a03df..aad73c8209 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -22,22 +22,10 @@ catalog: yourProviderId: host: gitlab-host # Identifies one of the hosts set up in the integrations branch: main # Optional. Uses `master` as default - group: example-group # Group and subgroup (if needed) to look for repositories + group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` ``` -If you desire so, it's also possible to execute the gitlab discovery in your entire -project. In order to do that, use this catalog configuration instead: - -```yaml -catalog: - providers: - gitlab: - host: gitlab-host # Identifies one of the hosts set up in the integrations - branch: main # Optional. Uses `master` as default - entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` -``` - As this provider is not one of the default providers, you will first need to install the gitlab catalog plugin: diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 0626cb9488..82f1a5db7c 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -23,53 +23,34 @@ export interface Config { /** * GitlabDiscoveryEntityProvider configuration */ - gitlab?: - | { - /** - * (Required) Gitlab's host name. - * @visibility backend - */ - host: string; - /** - * (Optional) Default branch to read the catalog-info.yaml file. - * If not set, 'master' will be used. - * @visibility backend - */ - branch?: string; - /** - * (Optional) The name used for the catalog file. - * If not set, 'catalog-info.yaml' will be used. - * @visibility backend - */ - entityFilename?: string; - } - | Record< - string, - { - /** - * (Required) Gitlab's host name. - * @visibility backend - */ - host: string; - /** - * (Required) Gitlab's group[/subgroup] where the discovery is done. - * @visibility backend - */ - group: string; - /** - * (Optional) Default branch to read the catalog-info.yaml file. - * If not set, 'master' will be used. - * @visibility backend - */ - branch?: string; - /** - * (Optional) The name used for the catalog file. - * If not set, 'catalog-info.yaml' will be used. - * @visibility backend - */ - entityFilename?: string; - } - >; + gitlab?: Record< + string, + { + /** + * (Required) Gitlab's host name. + * @visibility backend + */ + host: string; + /** + * (Optional) Gitlab's group[/subgroup] where the discovery is done. + * If not defined the whole project will be scanned. + * @visibility backend + */ + group?: string; + /** + * (Optional) Default branch to read the catalog-info.yaml file. + * If not set, 'master' will be used. + * @visibility backend + */ + branch?: string; + /** + * (Optional) The name used for the catalog file. + * If not set, 'catalog-info.yaml' will be used. + * @visibility backend + */ + entityFilename?: string; + } + >; }; }; } diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index 429743f4bd..892ce5f25c 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -100,7 +100,7 @@ describe('config', () => { }); expect(() => readGitlabConfigs(config)).toThrow( - "Missing required config value at 'catalog.providers.gitlab.test.group'", + "Missing required config value at 'catalog.providers.gitlab.test.host'", ); }); @@ -109,8 +109,10 @@ describe('config', () => { catalog: { providers: { gitlab: { - host: 'host', - branch: 'main', + test: { + host: 'host', + branch: 'main', + }, }, }, }, diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index 56df8d62f1..d6ad28b2ca 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -25,7 +25,7 @@ import { GitlabProviderConfig } from '../lib/types'; * @param config - The config object to extract from */ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { - const group = config.getString('group'); + const group = config.getOptionalString('group') ?? ''; const host = config.getString('host'); const branch = config.getOptionalString('branch') ?? 'master'; const catalogFile = @@ -48,7 +48,6 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { * @param config - The config object to extract from */ export function readGitlabConfigs(config: Config): GitlabProviderConfig[] { - const reservedParams = ['host', 'group', 'branch', 'catalogFile']; const configs: GitlabProviderConfig[] = []; const providerConfigs = config.getOptionalConfig('catalog.providers.gitlab'); @@ -57,20 +56,8 @@ export function readGitlabConfigs(config: Config): GitlabProviderConfig[] { return configs; } - if (providerConfigs.keys().some(r => reservedParams.indexOf(r) >= 0)) { - configs.push({ - id: 'full-check', - group: '', - branch: providerConfigs.getOptionalString('branch') ?? 'master', - host: providerConfigs.getString('host'), - catalogFile: - providerConfigs.getOptionalString('entityFilename') ?? - 'catalog-info.yaml', - }); - } else { - for (const id of providerConfigs.keys()) { - configs.push(readGitlabConfig(id, providerConfigs.getConfig(id))); - } + for (const id of providerConfigs.keys()) { + configs.push(readGitlabConfig(id, providerConfigs.getConfig(id))); } return configs; From bce462d4e326f120fb18b5320537ec2628a516e9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jun 2022 07:45:37 +0000 Subject: [PATCH 16/73] fix(deps): update dependency @microsoft/microsoft-graph-types to v2.22.0 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bca39feaef..61a9d59a1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4183,9 +4183,9 @@ typescript "~4.6.3" "@microsoft/microsoft-graph-types@^2.6.0": - version "2.21.0" - resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.21.0.tgz#3244b41374307f590948e25fb2de8dad2a8a81c9" - integrity sha512-ZbYetXyUqHZliM8exZzF7IPEKVMvR+PaY+P9bjNHpxeiacF3U9+c40YRv0TJHpF6+50GWmJmPD6BsKHVRBZiCA== + version "2.22.0" + resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.22.0.tgz#7ceb222311770ed5b240d627b657d25b3979964e" + integrity sha512-iKc5L036hc/wu13DX5kGf//3/WqTAErAPTyYKG6zT3vG070xXaaP7PInODznfyZduhiOvavmrRlQb34ajeDTUA== "@microsoft/tsdoc-config@~0.16.1": version "0.16.1" From c8a502ce3e0354d87f6b5cb42acde1d744cd66b7 Mon Sep 17 00:00:00 2001 From: Daniele Mazzotta Date: Tue, 21 Jun 2022 17:36:35 +0400 Subject: [PATCH 17/73] fixed formatting Signed-off-by: Daniele Mazzotta --- plugins/apache-airflow/api-report.md | 4 ++-- .../src/components/DagTableComponent/DagTableComponent.tsx | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/apache-airflow/api-report.md b/plugins/apache-airflow/api-report.md index 09123de613..333e4543cf 100644 --- a/plugins/apache-airflow/api-report.md +++ b/plugins/apache-airflow/api-report.md @@ -5,8 +5,8 @@ ```ts /// -import {BackstagePlugin} from '@backstage/core-plugin-api'; -import {RouteRef} from '@backstage/core-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; // @public export const ApacheAirflowDagTable: ({ diff --git a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx index 7619b06497..ff7b961c48 100644 --- a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx +++ b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx @@ -164,14 +164,12 @@ export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { return ( <> - {dagsNotFound ? ( + {dagsNotFound && ( {dagsNotFound.map(dagId => ( {dagId} ))} - ) : ( - '' )} From d03b2c4ceb8328d427f170d4d93dc9b8a4eca554 Mon Sep 17 00:00:00 2001 From: Taras Date: Mon, 20 Jun 2022 16:37:38 -0400 Subject: [PATCH 18/73] Added Humanitec plugin Signed-off-by: Taras Mankovski Signed-off-by: Taras --- microsite/data/plugins/humanitec.yaml | 11 +++++++++++ microsite/static/img/humanitec-logo.png | Bin 0 -> 7088 bytes 2 files changed, 11 insertions(+) create mode 100644 microsite/data/plugins/humanitec.yaml create mode 100644 microsite/static/img/humanitec-logo.png diff --git a/microsite/data/plugins/humanitec.yaml b/microsite/data/plugins/humanitec.yaml new file mode 100644 index 0000000000..88c0cd0a08 --- /dev/null +++ b/microsite/data/plugins/humanitec.yaml @@ -0,0 +1,11 @@ +--- +title: Humanitec Platform Orchestrator +author: Frontside +authorUrl: 'https://frontside.com' +category: Deployment # A single category e.g. CI, Machine Learning, Services, Monitoring +description: | + Show workloads, environments and resources deployed by Humanitec Platform Orchestrator. + Plugin includes an Entity ComponentCard, Backend API route and scaffolder actions. +documentation: https://github.com/thefrontside/backstage/tree/main/plugins/humanitec +iconUrl: ../../static/img/humanitec-logo.png +npmPackageName: '@frontside/backstage-plugin-humanitec' diff --git a/microsite/static/img/humanitec-logo.png b/microsite/static/img/humanitec-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..69ab02e9cd481ad93d6d8539ed9d2de1677b834b GIT binary patch literal 7088 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 a3Q Date: Tue, 21 Jun 2022 17:43:54 +0200 Subject: [PATCH 19/73] moved entity checkers to catalog-model Signed-off-by: Alex Rybchenko --- .../src/entity/conditions.test.ts | 172 ++++++++++++++++++ .../catalog-model/src/entity/conditions.ts | 76 ++++++++ packages/catalog-model/src/entity/index.ts | 1 + .../src/search/DefaultCatalogCollator.ts | 8 +- plugins/catalog-backend/src/search/util.ts | 10 +- .../src/components/EntitySwitch/conditions.ts | 61 +------ 6 files changed, 253 insertions(+), 75 deletions(-) create mode 100644 packages/catalog-model/src/entity/conditions.test.ts create mode 100644 packages/catalog-model/src/entity/conditions.ts diff --git a/packages/catalog-model/src/entity/conditions.test.ts b/packages/catalog-model/src/entity/conditions.test.ts new file mode 100644 index 0000000000..046f1c60e4 --- /dev/null +++ b/packages/catalog-model/src/entity/conditions.test.ts @@ -0,0 +1,172 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from './Entity'; +import { + isApiEntity, + isComponentEntity, + isDomainEntity, + isGroupEntity, + isLocationEntity, + isResourceEntity, + isSystemEntity, + isUserEntity, +} from './conditions'; + +const apiEntity: Entity = { + apiVersion: '', + kind: 'api', + metadata: { name: 'api' }, +}; +const componentEntity: Entity = { + apiVersion: '', + kind: 'component', + metadata: { name: 'Component' }, +}; +const domainEntity: Entity = { + apiVersion: '', + kind: 'domain', + metadata: { name: 'Domain' }, +}; +const groupEntity: Entity = { + apiVersion: '', + kind: 'group', + metadata: { name: 'Group' }, +}; +const locationEntity: Entity = { + apiVersion: '', + kind: 'location', + metadata: { name: 'Location' }, +}; +const resourceEntity: Entity = { + apiVersion: '', + kind: 'resource', + metadata: { name: 'Resource' }, +}; +const systemEntity: Entity = { + apiVersion: '', + kind: 'system', + metadata: { name: 'System' }, +}; +const userEntity: Entity = { + apiVersion: '', + kind: 'user', + metadata: { name: 'User' }, +}; + +describe('isApiEntity', () => { + it('should check for the intended type', () => { + expect(isApiEntity(componentEntity)).not.toBeTruthy(); + expect(isApiEntity(domainEntity)).not.toBeTruthy(); + expect(isApiEntity(groupEntity)).not.toBeTruthy(); + expect(isApiEntity(locationEntity)).not.toBeTruthy(); + expect(isApiEntity(resourceEntity)).not.toBeTruthy(); + expect(isApiEntity(systemEntity)).not.toBeTruthy(); + expect(isApiEntity(userEntity)).not.toBeTruthy(); + expect(isApiEntity(apiEntity)).toBeTruthy(); + }); +}); + +describe('isComponentEntity', () => { + it('should check for the intended type', () => { + expect(isComponentEntity(apiEntity)).not.toBeTruthy(); + expect(isComponentEntity(domainEntity)).not.toBeTruthy(); + expect(isComponentEntity(groupEntity)).not.toBeTruthy(); + expect(isComponentEntity(locationEntity)).not.toBeTruthy(); + expect(isComponentEntity(resourceEntity)).not.toBeTruthy(); + expect(isComponentEntity(systemEntity)).not.toBeTruthy(); + expect(isComponentEntity(userEntity)).not.toBeTruthy(); + expect(isComponentEntity(componentEntity)).toBeTruthy(); + }); +}); + +describe('isDomainEntity', () => { + it('should check for the intended type', () => { + expect(isDomainEntity(apiEntity)).not.toBeTruthy(); + expect(isDomainEntity(componentEntity)).not.toBeTruthy(); + expect(isDomainEntity(groupEntity)).not.toBeTruthy(); + expect(isDomainEntity(locationEntity)).not.toBeTruthy(); + expect(isDomainEntity(resourceEntity)).not.toBeTruthy(); + expect(isDomainEntity(systemEntity)).not.toBeTruthy(); + expect(isDomainEntity(userEntity)).not.toBeTruthy(); + expect(isDomainEntity(domainEntity)).toBeTruthy(); + }); +}); + +describe('isGroupEntity', () => { + it('should check for the intended type', () => { + expect(isGroupEntity(apiEntity)).not.toBeTruthy(); + expect(isGroupEntity(componentEntity)).not.toBeTruthy(); + expect(isGroupEntity(domainEntity)).not.toBeTruthy(); + expect(isGroupEntity(locationEntity)).not.toBeTruthy(); + expect(isGroupEntity(resourceEntity)).not.toBeTruthy(); + expect(isGroupEntity(systemEntity)).not.toBeTruthy(); + expect(isGroupEntity(userEntity)).not.toBeTruthy(); + expect(isGroupEntity(groupEntity)).toBeTruthy(); + }); +}); + +describe('isLocationEntity', () => { + it('should check for the intended type', () => { + expect(isLocationEntity(apiEntity)).not.toBeTruthy(); + expect(isLocationEntity(componentEntity)).not.toBeTruthy(); + expect(isLocationEntity(domainEntity)).not.toBeTruthy(); + expect(isLocationEntity(groupEntity)).not.toBeTruthy(); + expect(isLocationEntity(resourceEntity)).not.toBeTruthy(); + expect(isLocationEntity(systemEntity)).not.toBeTruthy(); + expect(isLocationEntity(userEntity)).not.toBeTruthy(); + expect(isLocationEntity(locationEntity)).toBeTruthy(); + }); +}); + +describe('isResourceEntity', () => { + it('should check for the intended type', () => { + expect(isResourceEntity(apiEntity)).not.toBeTruthy(); + expect(isResourceEntity(componentEntity)).not.toBeTruthy(); + expect(isResourceEntity(domainEntity)).not.toBeTruthy(); + expect(isResourceEntity(groupEntity)).not.toBeTruthy(); + expect(isResourceEntity(locationEntity)).not.toBeTruthy(); + expect(isResourceEntity(systemEntity)).not.toBeTruthy(); + expect(isResourceEntity(userEntity)).not.toBeTruthy(); + expect(isResourceEntity(resourceEntity)).toBeTruthy(); + }); +}); + +describe('isSystemEntity', () => { + it('should check for the intended type', () => { + expect(isSystemEntity(apiEntity)).not.toBeTruthy(); + expect(isSystemEntity(componentEntity)).not.toBeTruthy(); + expect(isSystemEntity(domainEntity)).not.toBeTruthy(); + expect(isSystemEntity(groupEntity)).not.toBeTruthy(); + expect(isSystemEntity(locationEntity)).not.toBeTruthy(); + expect(isSystemEntity(resourceEntity)).not.toBeTruthy(); + expect(isSystemEntity(userEntity)).not.toBeTruthy(); + expect(isSystemEntity(systemEntity)).toBeTruthy(); + }); +}); + +describe('isUserEntity', () => { + it('should check for the intended type', () => { + expect(isUserEntity(apiEntity)).not.toBeTruthy(); + expect(isUserEntity(componentEntity)).not.toBeTruthy(); + expect(isUserEntity(domainEntity)).not.toBeTruthy(); + expect(isUserEntity(groupEntity)).not.toBeTruthy(); + expect(isUserEntity(locationEntity)).not.toBeTruthy(); + expect(isUserEntity(resourceEntity)).not.toBeTruthy(); + expect(isUserEntity(systemEntity)).not.toBeTruthy(); + expect(isUserEntity(userEntity)).toBeTruthy(); + }); +}); diff --git a/packages/catalog-model/src/entity/conditions.ts b/packages/catalog-model/src/entity/conditions.ts new file mode 100644 index 0000000000..d761695b5e --- /dev/null +++ b/packages/catalog-model/src/entity/conditions.ts @@ -0,0 +1,76 @@ +/* + * 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 { + ApiEntity, + ComponentEntity, + DomainEntity, + GroupEntity, + LocationEntity, + ResourceEntity, + SystemEntity, + UserEntity, +} from '../kinds'; +import { Entity } from './Entity'; + +/** + * @public + */ +export function isApiEntity(entity: Entity): entity is ApiEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'API'; +} +/** + * @public + */ +export function isComponentEntity(entity: Entity): entity is ComponentEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'COMPONENT'; +} +/** + * @public + */ +export function isDomainEntity(entity: Entity): entity is DomainEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'DOMAIN'; +} +/** + * @public + */ +export function isGroupEntity(entity: Entity): entity is GroupEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'GROUP'; +} +/** + * @public + */ +export function isLocationEntity(entity: Entity): entity is LocationEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'LOCATION'; +} +/** + * @public + */ +export function isResourceEntity(entity: Entity): entity is ResourceEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'RESOURCE'; +} +/** + * @public + */ +export function isSystemEntity(entity: Entity): entity is SystemEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'SYSTEM'; +} +/** + * @public + */ +export function isUserEntity(entity: Entity): entity is UserEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'USER'; +} diff --git a/packages/catalog-model/src/entity/index.ts b/packages/catalog-model/src/entity/index.ts index 45ee673167..039529bf3e 100644 --- a/packages/catalog-model/src/entity/index.ts +++ b/packages/catalog-model/src/entity/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './conditions'; export { DEFAULT_NAMESPACE, ANNOTATION_EDIT_URL, diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index f895ded85e..70ec0ee948 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -20,8 +20,8 @@ import { } from '@backstage/backend-common'; import { Entity, + isUserEntity, stringifyEntityRef, - UserEntity, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -93,13 +93,9 @@ export class DefaultCatalogCollator { return formatted.toLowerCase(); } - private isUserEntity(entity: Entity): entity is UserEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'USER'; - } - private getDocumentText(entity: Entity): string { let documentText = entity.metadata.description || ''; - if (this.isUserEntity(entity)) { + if (isUserEntity(entity)) { if (entity.spec?.profile?.displayName && documentText) { // combine displayName and description const displayName = entity.spec?.profile?.displayName; diff --git a/plugins/catalog-backend/src/search/util.ts b/plugins/catalog-backend/src/search/util.ts index b5df2a28a7..fd54d397c9 100644 --- a/plugins/catalog-backend/src/search/util.ts +++ b/plugins/catalog-backend/src/search/util.ts @@ -14,15 +14,7 @@ * limitations under the License. */ -import { Entity, UserEntity, GroupEntity } from '@backstage/catalog-model'; - -function isUserEntity(entity: Entity): entity is UserEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'USER'; -} - -function isGroupEntity(entity: Entity): entity is GroupEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'GROUP'; -} +import { Entity, isUserEntity, isGroupEntity } from '@backstage/catalog-model'; export function getDocumentText(entity: Entity): string { const documentTexts: string[] = []; diff --git a/plugins/catalog/src/components/EntitySwitch/conditions.ts b/plugins/catalog/src/components/EntitySwitch/conditions.ts index b6837627c0..86fd38a900 100644 --- a/plugins/catalog/src/components/EntitySwitch/conditions.ts +++ b/plugins/catalog/src/components/EntitySwitch/conditions.ts @@ -14,17 +14,7 @@ * limitations under the License. */ -import { - ApiEntity, - ComponentEntity, - DomainEntity, - Entity, - GroupEntity, - LocationEntity, - ResourceEntity, - SystemEntity, - UserEntity, -} from '@backstage/catalog-model'; +import { ComponentEntity, Entity } from '@backstage/catalog-model'; function strCmp(a: string | undefined, b: string | undefined): boolean { return Boolean( @@ -67,52 +57,3 @@ export function isComponentType(types: string | string[]) { export function isNamespace(namespaces: string | string[]) { return (entity: Entity) => strCmpAll(entity.metadata?.namespace, namespaces); } - -/** - * @public - */ -export function isApiEntity(entity: Entity): entity is ApiEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'API'; -} -/** - * @public - */ -export function isComponentEntity(entity: Entity): entity is ComponentEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'COMPONENT'; -} -/** - * @public - */ -export function isDomainEntity(entity: Entity): entity is DomainEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'DOMAIN'; -} -/** - * @public - */ -export function isGroupEntity(entity: Entity): entity is GroupEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'GROUP'; -} -/** - * @public - */ -export function isLocationEntity(entity: Entity): entity is LocationEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'LOCATION'; -} -/** - * @public - */ -export function isResourceEntity(entity: Entity): entity is ResourceEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'RESOURCE'; -} -/** - * @public - */ -export function isSystemEntity(entity: Entity): entity is SystemEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'SYSTEM'; -} -/** - * @public - */ -export function isUserEntity(entity: Entity): entity is UserEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'USER'; -} From 919396b27aa75491dd45d372ee64e09256484b5c Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 21 Jun 2022 17:47:18 +0200 Subject: [PATCH 20/73] revert plugin-catalog export change Signed-off-by: Alex Rybchenko --- plugins/catalog/src/components/EntitySwitch/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/EntitySwitch/index.ts b/plugins/catalog/src/components/EntitySwitch/index.ts index 5d0537bfa5..6cccf4be6e 100644 --- a/plugins/catalog/src/components/EntitySwitch/index.ts +++ b/plugins/catalog/src/components/EntitySwitch/index.ts @@ -16,4 +16,4 @@ export { EntitySwitch } from './EntitySwitch'; export type { EntitySwitchProps, EntitySwitchCaseProps } from './EntitySwitch'; -export * from './conditions'; +export { isKind, isNamespace, isComponentType } from './conditions'; From f1dcc6f3c684bb679a4cecb9a3e557c1115103a2 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 21 Jun 2022 18:01:21 +0200 Subject: [PATCH 21/73] added changesets Signed-off-by: Alex Rybchenko --- .changeset/pretty-masks-live.md | 5 +++++ .changeset/silent-coats-brake.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/pretty-masks-live.md create mode 100644 .changeset/silent-coats-brake.md diff --git a/.changeset/pretty-masks-live.md b/.changeset/pretty-masks-live.md new file mode 100644 index 0000000000..c45ad1e77f --- /dev/null +++ b/.changeset/pretty-masks-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': patch +--- + +Added type predicates for all entity types, e.g. isUserEntity diff --git a/.changeset/silent-coats-brake.md b/.changeset/silent-coats-brake.md new file mode 100644 index 0000000000..b1cfbacb6a --- /dev/null +++ b/.changeset/silent-coats-brake.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-backend': patch +--- + +Use entity type predicates from catalog-model From 3db6c21390ea33c9db24f322bec45ea842dbccfd Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 21 Jun 2022 18:34:52 +0200 Subject: [PATCH 22/73] updated api-report Signed-off-by: Alex Rybchenko --- packages/catalog-model/api-report.md | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index fca23d6ab9..9ea7636de5 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -254,6 +254,36 @@ export { GroupEntityV1alpha1 }; // @public export const groupEntityV1alpha1Validator: KindValidator; +// @public (undocumented) +export function isApiEntity(entity: Entity): entity is ApiEntityV1alpha1; + +// @public (undocumented) +export function isComponentEntity( + entity: Entity, +): entity is ComponentEntityV1alpha1; + +// @public (undocumented) +export function isDomainEntity(entity: Entity): entity is DomainEntityV1alpha1; + +// @public (undocumented) +export function isGroupEntity(entity: Entity): entity is GroupEntityV1alpha1; + +// @public (undocumented) +export function isLocationEntity( + entity: Entity, +): entity is LocationEntityV1alpha1; + +// @public (undocumented) +export function isResourceEntity( + entity: Entity, +): entity is ResourceEntityV1alpha1; + +// @public (undocumented) +export function isSystemEntity(entity: Entity): entity is SystemEntityV1alpha1; + +// @public (undocumented) +export function isUserEntity(entity: Entity): entity is UserEntityV1alpha1; + // @public export type KindValidator = { check(entity: Entity): Promise; From 7f18a4245043d04511d1d966163b3f6dd5c7bdf3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jun 2022 16:55:38 +0000 Subject: [PATCH 23/73] fix(deps): update dependency logform to v2.4.1 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6841fc4366..2dbf176e9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17554,9 +17554,9 @@ log-update@^4.0.0: wrap-ansi "^6.2.0" logform@^2.3.2, logform@^2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe" - integrity sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw== + version "2.4.1" + resolved "https://registry.npmjs.org/logform/-/logform-2.4.1.tgz#512c9eaef738044d1c619790ba0f806c80d9d3a9" + integrity sha512-7XB/tqc3VRbri9pRjU6E97mQ8vC27ivJ3lct4jhyT+n0JNDd4YKldFl0D75NqDp46hk8RC7Ma1Vjv/UPf67S+A== dependencies: "@colors/colors" "1.5.0" fecha "^4.2.0" From cb37b69827818cd8cc83f3a3e6d31fdedea7ffd2 Mon Sep 17 00:00:00 2001 From: Clare Liguori Date: Tue, 21 Jun 2022 10:26:23 -0700 Subject: [PATCH 24/73] Add AWS Proton to plugin marketplace Signed-off-by: Clare Liguori --- microsite/data/plugins/aws-proton.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 microsite/data/plugins/aws-proton.yaml diff --git a/microsite/data/plugins/aws-proton.yaml b/microsite/data/plugins/aws-proton.yaml new file mode 100644 index 0000000000..022b553b24 --- /dev/null +++ b/microsite/data/plugins/aws-proton.yaml @@ -0,0 +1,9 @@ +--- +title: AWS Proton +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Infrastructure +description: Create and view AWS Proton services for your components in Backstage. +documentation: https://github.com/awslabs/aws-proton-plugins-for-backstage#readme +iconUrl: https://github.com/awslabs/aws-proton-plugins-for-backstage/blob/main/docs/images/proton-logo.png?raw=true +npmPackageName: '@aws/aws-proton-plugin-for-backstage' From 7e115d42f945e57e2fbcf4d83ef69e89362753f4 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Tue, 21 Jun 2022 16:34:55 -0400 Subject: [PATCH 25/73] refactor(MyGroupsSidebarItem): render namespaces with subtitles Signed-off-by: Phil Kuang --- .changeset/serious-zebras-joke.md | 5 +++ .changeset/shiny-turkeys-doubt.md | 2 +- packages/core-components/api-report.md | 1 + .../src/layout/Sidebar/SidebarSubmenuItem.tsx | 23 ++++++++++++- .../MyGroupsSidebarItem.tsx | 32 ++++--------------- 5 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 .changeset/serious-zebras-joke.md diff --git a/.changeset/serious-zebras-joke.md b/.changeset/serious-zebras-joke.md new file mode 100644 index 0000000000..78f38ca967 --- /dev/null +++ b/.changeset/serious-zebras-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Support displaying subtitle text in `SidebarSubmenuItem` diff --git a/.changeset/shiny-turkeys-doubt.md b/.changeset/shiny-turkeys-doubt.md index 0f663a4c74..edac39182c 100644 --- a/.changeset/shiny-turkeys-doubt.md +++ b/.changeset/shiny-turkeys-doubt.md @@ -2,4 +2,4 @@ '@backstage/plugin-org': patch --- -Render namespaced teams within dropdown items in `MyGroupsSidebarItem` +Render namespaces for teams with subtitles in `MyGroupsSidebarItem` diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 911ef36867..302ef7ef7d 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1110,6 +1110,7 @@ export type SidebarSubmenuItemDropdownItem = { // @public export type SidebarSubmenuItemProps = { title: string; + subtitle?: string; to?: string; icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index 41f1a1e287..a1565a348f 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -59,6 +59,13 @@ const useStyles = makeStyles( whiteSpace: 'nowrap', overflow: 'hidden', 'text-overflow': 'ellipsis', + lineHeight: 1, + }, + subtitle: { + fontSize: 10, + whiteSpace: 'nowrap', + overflow: 'hidden', + 'text-overflow': 'ellipsis', }, dropdownArrow: { position: 'absolute', @@ -105,6 +112,7 @@ export type SidebarSubmenuItemDropdownItem = { * Holds submenu item content. * * title: Text content of submenu item + * subtitle: A subtitle displayed under the main title * to: Path to navigate to when item is clicked * icon: Icon displayed on the left of text content * dropdownItems: Optional array of dropdown items displayed when submenu item is clicked. @@ -113,6 +121,7 @@ export type SidebarSubmenuItemDropdownItem = { */ export type SidebarSubmenuItemProps = { title: string; + subtitle?: string; to?: string; icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; @@ -124,7 +133,7 @@ export type SidebarSubmenuItemProps = { * @public */ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { - const { title, to, icon: Icon, dropdownItems } = props; + const { title, subtitle, to, icon: Icon, dropdownItems } = props; const classes = useStyles(); const { setIsHoveredOn } = useContext(SidebarItemWithSubmenuContext); const closeSubmenu = () => { @@ -158,6 +167,12 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { {Icon && } {title} +
+ {subtitle && ( + + {subtitle} + + )}
{showDropDown ? ( @@ -210,6 +225,12 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { {Icon && } {title} +
+ {subtitle && ( + + {subtitle} + + )}
diff --git a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx index 445bf8e6fe..f3e3a6bd9a 100644 --- a/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx +++ b/plugins/org/src/components/MyGroupsSidebarItem/MyGroupsSidebarItem.tsx @@ -17,7 +17,6 @@ import React from 'react'; import { DEFAULT_NAMESPACE, - Entity, stringifyEntityRef, } from '@backstage/catalog-model'; import { @@ -90,42 +89,25 @@ export const MyGroupsSidebarItem = (props: { ); } - const namespacedGroupsMap: { [namespace: string]: Entity[] } = {}; - groups.forEach(group => { - namespacedGroupsMap[group.metadata.namespace!] = - namespacedGroupsMap[group.metadata.namespace!] ?? []; - namespacedGroupsMap[group.metadata.namespace!].push(group); - }); - // Member of more than one group return ( - {namespacedGroupsMap[DEFAULT_NAMESPACE]?.map(group => { + {groups?.map(function groupsMap(group) { return ( ); })} - {Object.entries(namespacedGroupsMap) - .filter(([n]) => n !== DEFAULT_NAMESPACE) - .map(([namespace, namespacedGroups]) => { - return ( - ({ - title: group.metadata.title || group.metadata.name, - to: catalogEntityRoute(getCompoundEntityRef(group)), - }))} - /> - ); - })} ); From 0ae5d7858d364073caa0a37de17cfa1d7ace5913 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Tue, 21 Jun 2022 17:29:35 -0500 Subject: [PATCH 26/73] Fixed Azure Discovery config example Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- docs/integrations/azure/discovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index c607a6550e..6a420462c0 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -45,7 +45,7 @@ catalog: anotherProviderId: # another identifier organization: myorg project: myproject - repository: '*' # this will match all repos starting with service-* + repository: '*' # this will match all repos path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder yetAotherProviderId: # guess, what? Another one :) host: selfhostedazure.yourcompany.com From bf0336352b2047409bd8ab6a07832f5ff09bc644 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 00:40:44 +0000 Subject: [PATCH 27/73] chore(deps): update dependency @types/testing-library__jest-dom to v5.14.5 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2c1a244a1f..f0c7f2ba35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7008,9 +7008,9 @@ terser-webpack-plugin "*" "@types/testing-library__jest-dom@^5.9.1": - version "5.14.4" - resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.4.tgz#21567ec845f4efee55923842c79728dc49c1650b" - integrity sha512-EUCs9PTBOEyfRtLKkKd31YrRCx/9Wxjy2Uqb6IH/KAOr7/vP0i8iClOyxQqjm/UxMGU5r5s2vOBM7vSPQVmabg== + version "5.14.5" + resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f" + integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ== dependencies: "@types/jest" "*" From 81c9d7fb4446e1ced660277d394783f84d1117c0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 00:41:30 +0000 Subject: [PATCH 28/73] fix(deps): update dependency @uiw/react-codemirror to v4.9.4 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2c1a244a1f..9e3320b676 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7317,9 +7317,9 @@ eslint-visitor-keys "^3.0.0" "@uiw/react-codemirror@^4.9.3": - version "4.9.3" - resolved "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.9.3.tgz#3d7abc434b140f01119332e25e9f49ace7c381bf" - integrity sha512-iT/daKF852ZsNJGvqmxRFJ4KTOJSikeoCMl7jhUUJTETdozUf0/DqUQhvazAoduMRzqqHYnN/5isdITCyy2P6g== + version "4.9.4" + resolved "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.9.4.tgz#4643201f279fed103f2cf15df9431931fe4e9f15" + integrity sha512-IsC5xDevpIeLMzHQQwT2W40gFFIdKeT1T0DHjzzai+s5SIrMlGe3QSHWeC1wSO7FtfNxFpFlTYMGJm5JwUviMA== dependencies: "@babel/runtime" ">=7.11.0" "@codemirror/theme-one-dark" "^6.0.0" From ed40cd0278f8ae46f488c3645a91016ac1158f3b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 01:51:26 +0000 Subject: [PATCH 29/73] fix(deps): update dependency google-auth-library to v8.0.3 Signed-off-by: Renovate Bot --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2793bc7ba7..c4640b76b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13884,9 +13884,9 @@ google-auth-library@^7.14.0: lru-cache "^6.0.0" google-auth-library@^8.0.0, google-auth-library@^8.0.1, google-auth-library@^8.0.2: - version "8.0.2" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.0.2.tgz#5fa0f2d3795c3e4019d2bb315ade4454cc9c30b5" - integrity sha512-HoG+nWFAThLovKpvcbYzxgn+nBJPTfAwtq0GxPN821nOO+21+8oP7MoEHfd1sbDulUFFGfcjJr2CnJ4YssHcyg== + version "8.0.3" + resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.0.3.tgz#1e780430632d03df36dc22a3f5c89dbc251f2105" + integrity sha512-1eC6yaCrPfkv3bwtb3e0AOct7E7xR/uikDyXNo/j8Wd6a1ldRgAey5FmaDGNJnHNDPLtDiENQLYsA69eXOF5sA== dependencies: arrify "^2.0.0" base64-js "^1.3.0" @@ -13894,7 +13894,7 @@ google-auth-library@^8.0.0, google-auth-library@^8.0.1, google-auth-library@^8.0 fast-text-encoding "^1.0.0" gaxios "^5.0.0" gcp-metadata "^5.0.0" - gtoken "^5.3.2" + gtoken "^6.0.0" jws "^4.0.0" lru-cache "^6.0.0" @@ -13943,10 +13943,10 @@ google-p12-pem@^3.0.3: dependencies: node-forge "^1.0.0" -google-p12-pem@^3.1.3: - version "3.1.4" - resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz#123f7b40da204de4ed1fbf2fd5be12c047fc8b3b" - integrity sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg== +google-p12-pem@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.0.tgz#f46581add1dc6ea0b96160cda6ce37ee35ab8ca3" + integrity sha512-lRTMn5ElBdDixv4a86bixejPSRk1boRtUowNepeKEVvYiFlkLuAJUVpEz6PfObDHYEKnZWq/9a2zC98xu62A9w== dependencies: node-forge "^1.3.1" @@ -14145,13 +14145,13 @@ gtoken@^5.0.4: jws "^4.0.0" mime "^2.2.0" -gtoken@^5.3.2: - version "5.3.2" - resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz#deb7dc876abe002178e0515e383382ea9446d58f" - integrity sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ== +gtoken@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/gtoken/-/gtoken-6.0.1.tgz#1276371d51e93c4eface76e3f30f9e8f1cad3f1a" + integrity sha512-J0vebk6u6i4rLTM0lQq25SdusCLMvujYNZeAouyPvSbGlcjw7P8L3W9INIFnlXUx+AUD7TDoM1mgdhzH+XX7DQ== dependencies: gaxios "^4.0.0" - google-p12-pem "^3.1.3" + google-p12-pem "^4.0.0" jws "^4.0.0" gzip-size@^6.0.0: From e7ce3f708442eea242e08c08d63a667744137cf3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 01:54:37 +0000 Subject: [PATCH 30/73] fix(deps): update dependency sucrase to v3.21.1 Signed-off-by: Renovate Bot --- storybook/yarn.lock | 17 ++++++----------- yarn.lock | 6 +++--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 39d0e2725f..6bf41324eb 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -2610,7 +2610,7 @@ ansi-to-html@^0.6.11: any-promise@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@^2.0.0: version "2.0.0" @@ -7039,12 +7039,7 @@ pinkie@^2.0.0: resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== -pirates@^4.0.1: - version "4.0.4" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" - integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== - -pirates@^4.0.5: +pirates@^4.0.1, pirates@^4.0.5: version "4.0.5" resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== @@ -8392,9 +8387,9 @@ style-to-object@0.3.0, style-to-object@^0.3.0: inline-style-parser "0.1.1" sucrase@^3.21.0: - version "3.21.0" - resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.21.0.tgz#6a5affdbe716b22e4dc99c57d366ad0d216444b9" - integrity sha512-FjAhMJjDcifARI7bZej0Bi1yekjWQHoEvWIXhLPwDhC6O4iZ5PtGb86WV56riW87hzpgB13wwBKO9vKAiWu5VQ== + version "3.21.1" + resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.21.1.tgz#7e29ddaca012764cf843280b00e74a843bdf790f" + integrity sha512-kxXnC9yZEav5USAu8gooZID9Ph3xqwdJxZoh+WbOWQZHTB7CHj3ANwENVMZ6mAZ9k7UtJtFxvQD9R03q3yU2YQ== dependencies: commander "^4.0.0" glob "7.1.6" @@ -8552,7 +8547,7 @@ test-exclude@^6.0.0: thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== dependencies: thenify ">= 3.1.0 < 4" diff --git a/yarn.lock b/yarn.lock index 2793bc7ba7..2a6c060e14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24153,9 +24153,9 @@ stylis@^4.0.6: integrity sha512-OFFeUXFgwnGOKvEXaSv0D0KQ5ADP0n6g3SVONx6I/85JzNZ3u50FRwB3lVIk1QO2HNdI75tbVzc4Z66Gdp9voA== sucrase@^3.18.0, sucrase@^3.20.2: - version "3.21.0" - resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.21.0.tgz#6a5affdbe716b22e4dc99c57d366ad0d216444b9" - integrity sha512-FjAhMJjDcifARI7bZej0Bi1yekjWQHoEvWIXhLPwDhC6O4iZ5PtGb86WV56riW87hzpgB13wwBKO9vKAiWu5VQ== + version "3.21.1" + resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.21.1.tgz#7e29ddaca012764cf843280b00e74a843bdf790f" + integrity sha512-kxXnC9yZEav5USAu8gooZID9Ph3xqwdJxZoh+WbOWQZHTB7CHj3ANwENVMZ6mAZ9k7UtJtFxvQD9R03q3yU2YQ== dependencies: commander "^4.0.0" glob "7.1.6" From 403c92438b522938b21f7c1354056b9c538d8c05 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 02:44:30 +0000 Subject: [PATCH 31/73] chore(deps): update dependency cypress to v10.2.0 Signed-off-by: Renovate Bot --- cypress/yarn.lock | 6 +++--- yarn.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cypress/yarn.lock b/cypress/yarn.lock index 0fc9da99b6..1ccce4ea24 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -304,9 +304,9 @@ cross-spawn@^7.0.0: which "^2.0.1" cypress@^10.0.0: - version "10.1.0" - resolved "https://registry.npmjs.org/cypress/-/cypress-10.1.0.tgz#6514a26c721822a02bc194e9a7f72c3142aea174" - integrity sha512-aQ4JVZVib4Xd9FZW8IRZfKelUvqF4y5A+oUbNvn8TlsBmEfIg3m5Xd6Mt6PVU/jHiVJ9Psl905B3ZPnrDcmyuQ== + version "10.2.0" + resolved "https://registry.npmjs.org/cypress/-/cypress-10.2.0.tgz#ca078abfceb13be2a33cbba6e0e80ded770f542a" + integrity sha512-+i9lY5ENlfi2mJwsggzR+XASOIgMd7S/Gd3/13NCpv596n3YSplMAueBTIxNLcxDpTcIksp+9pM3UaDrJDpFqA== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" diff --git a/yarn.lock b/yarn.lock index c4640b76b4..436199f240 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10719,9 +10719,9 @@ cypress-plugin-snapshots@^1.4.4: unidiff "1.0.2" cypress@^10.0.0: - version "10.1.0" - resolved "https://registry.npmjs.org/cypress/-/cypress-10.1.0.tgz#6514a26c721822a02bc194e9a7f72c3142aea174" - integrity sha512-aQ4JVZVib4Xd9FZW8IRZfKelUvqF4y5A+oUbNvn8TlsBmEfIg3m5Xd6Mt6PVU/jHiVJ9Psl905B3ZPnrDcmyuQ== + version "10.2.0" + resolved "https://registry.npmjs.org/cypress/-/cypress-10.2.0.tgz#ca078abfceb13be2a33cbba6e0e80ded770f542a" + integrity sha512-+i9lY5ENlfi2mJwsggzR+XASOIgMd7S/Gd3/13NCpv596n3YSplMAueBTIxNLcxDpTcIksp+9pM3UaDrJDpFqA== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" From c5270e31a269ba2e51e3a2cf7802a534c38f283b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 21 Jun 2022 22:49:29 -0400 Subject: [PATCH 32/73] Improve plural handling in log output Signed-off-by: Adam Harvey --- packages/backend-common/src/config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 94b362d99d..608869ea93 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -53,7 +53,9 @@ const updateRedactionList = ( ); logger.info( - `${values.size} secrets found in the config which will be redacted`, + `${values.size} secret${ + values.size > 1 ? 's' : '' + } found in the config which will be redacted`, ); setRootLoggerRedactionList(Array.from(values)); From 0fc57887e86d4ccbd3764d27a63a7a1c4286a2b7 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 21 Jun 2022 22:50:34 -0400 Subject: [PATCH 33/73] Add changeset Signed-off-by: Adam Harvey --- .changeset/quiet-pens-notice.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quiet-pens-notice.md diff --git a/.changeset/quiet-pens-notice.md b/.changeset/quiet-pens-notice.md new file mode 100644 index 0000000000..aa6aeb049d --- /dev/null +++ b/.changeset/quiet-pens-notice.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Improve plural handling in logging output for secrets From d821a2eec3269be542b27e28da5f8d1c61cb8eea Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 21 Jun 2022 23:08:41 -0400 Subject: [PATCH 34/73] Fix minor possessive typo Signed-off-by: Adam Harvey --- docs/getting-started/project-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/project-structure.md b/docs/getting-started/project-structure.md index cbe84db2e0..8dca898ae7 100644 --- a/docs/getting-started/project-structure.md +++ b/docs/getting-started/project-structure.md @@ -28,7 +28,7 @@ the code. sub-folder which is used for a markdown spellchecker. - [`.yarn/`](https://github.com/backstage/backstage/tree/master/.yarn) - - Backstage ships with it's own `yarn` implementation. This allows us to have + Backstage ships with its own `yarn` implementation. This allows us to have better control over our `yarn.lock` file and hopefully avoid problems due to yarn versioning differences. From d8e116964f70da1e2480ad93157b2b630cb94e01 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 21 Jun 2022 23:17:42 -0400 Subject: [PATCH 35/73] Fix broken deployment link Signed-off-by: Adam Harvey --- docs/FAQ.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 96d49ff060..f695a59410 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -223,8 +223,7 @@ For more information, see our No, this is not a service offering. We build the piece of software, and someone in your infrastructure team is responsible for -[deploying](https://backstage.io/docs/getting-started/deployment-k8s) and -maintaining it. +[deploying](https://backstage.io/docs/deployment) and maintaining it. ### How secure is Backstage? From 80542ee49cd308da40d7eb335a8884aa4b9d57cf Mon Sep 17 00:00:00 2001 From: nguyentranbao-ct Date: Wed, 22 Jun 2022 11:01:26 +0700 Subject: [PATCH 36/73] docs(adopters): add Cho Tot company Signed-off-by: nguyentranbao-ct --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 44e9dc9b44..8eacb45f83 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -183,3 +183,4 @@ _If you're using Backstage in your organization, please try to add your company | [Mercedes-Benz.io](https://www.mercedes-benz.io/) | [Manuel Santos](https://github.com/manusant) | At Mercedes-Benz we use it as a developer portal with software catalog, TechDocs, Scaffolding and custom plugins. It provides an overview of our tech ecosystem to our product development teams. The portal also serves as a way to foster collaboration among the numerous companies of the Mercedes-Benz Group. | [Funding Circle](https://www.fundingcircle.com/) | [Ariel Pacciaroni](https://github.com/arielpacciaroni) | We are building the internal developer portal using Backstage project and centralizing all services information at one place. The portal helps us track down repositories ownership as well as direct access to key information on every component. | [Clarivate](https://www.clarivate.com) | [Gabriele Carteni](mailto:gabriele.carteni@clarivate.com) | We are building our Developer Portal using Backstage to have a better control over our software ecosystem, integrate SDLC tools and promote best practices | +| [Cho Tot](https://www.chotot.com) | [Chotot Team](mailto:sre@chotot.vn) | Internal developer portal, service catalog with CI/CD tools. From bf44af8b41e37139f1661fa98f20760eb6212a8a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 07:43:55 +0000 Subject: [PATCH 37/73] fix(deps): update dependency aws-sdk to v2.1159.0 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 436199f240..c232257cd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8232,9 +8232,9 @@ aws-sdk-mock@^5.2.1: traverse "^0.6.6" aws-sdk@^2.1122.0, aws-sdk@^2.840.0, aws-sdk@^2.948.0: - version "2.1158.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1158.0.tgz#924fb6dda1acecb1dc1c8901095cf64d6acdade4" - integrity sha512-uHYzZMGE+b50sWXaLhga4aD1SpB3+DEZclAkg9aYz2pDZlSDTOMh3uJ/ufsMBs7VcDKGS7mQRibCmCbwRGTIlg== + version "2.1159.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1159.0.tgz#64d585ac608d58e6ff62678be71a4c4ca61ca5dc" + integrity sha512-zm3k/ufwZnkWc6M+HDz00CWuILot4L9kJ5VJsuDS9fwsT9To6k91Y1njCtIV4tcgcXvUru0Sbm4D0w5bc2847A== dependencies: buffer "4.9.2" events "1.1.1" From 1d7fe55f6fbf263a17ae6ebeb5aa8adfc39877c8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 08:06:52 +0000 Subject: [PATCH 38/73] fix(deps): update dependency graphql-modules to v2.1.0 Signed-off-by: Renovate Bot --- yarn.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 42c2af7d55..670073ba31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2597,7 +2597,7 @@ "@graphql-tools/utils" "8.6.13" tslib "^2.4.0" -"@graphql-tools/schema@8.3.10", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.1.1", "@graphql-tools/schema@^8.1.2", "@graphql-tools/schema@^8.3.1": +"@graphql-tools/schema@8.3.10", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.1.2", "@graphql-tools/schema@^8.3.1": version "8.3.10" resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.10.tgz#c3e373e6ad854f533fc7e55859dd8f9e81de30dd" integrity sha512-tfhjSTi3OzheDrVzG7rkPZg2BbQjmZRLM2vvQoM2b1TnUwgUIbpAgcnf+AWDLRsoCOWlezeLgij1BLeAR0Q0jg== @@ -14070,14 +14070,14 @@ graphql-language-service@^5.0.6: vscode-languageserver-types "^3.15.1" graphql-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/graphql-modules/-/graphql-modules-2.0.0.tgz#5f710d86eb7295da83769c8cd6d61a049d999800" - integrity sha512-CM5CIJp428+ripgcLyrioBmAKB3ucvIEOgJG4WMjnOgScHY/eCZh/8I51cF8oc+pqebOgBCR3HH//IH5v7kv+w== + version "2.1.0" + resolved "https://registry.npmjs.org/graphql-modules/-/graphql-modules-2.1.0.tgz#d99692034b4b053fba3d0ebe49e4e66772ed2785" + integrity sha512-fOUc4i5xNLkRxqx234MIr+kolrxk1tZ2onTeRIcB73mCY4NeKxej7FMLb5St+UcNLzhqM4L6Mf47rN9MPVDmgA== dependencies: - "@graphql-tools/schema" "^8.1.1" + "@graphql-tools/schema" "^8.3.1" "@graphql-tools/wrap" "^8.3.1" "@graphql-typed-document-node/core" "^3.1.0" - ramda "^0.27.1" + ramda "^0.28.0" graphql-request@^4.0.0: version "4.2.0" @@ -21412,10 +21412,10 @@ raf@^3.4.0: dependencies: performance-now "^2.1.0" -ramda@^0.27.1: - version "0.27.2" - resolved "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" - integrity sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA== +ramda@^0.28.0: + version "0.28.0" + resolved "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97" + integrity sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA== randexp@^0.5.3: version "0.5.3" From 6cf1fe4bc4b35b0cc02ed71c09082101bce76d1c Mon Sep 17 00:00:00 2001 From: Andrea Giannantonio Date: Wed, 22 Jun 2022 11:36:16 +0200 Subject: [PATCH 39/73] fix: adopters table border Signed-off-by: Andrea Giannantonio --- ADOPTERS.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 8eacb45f83..e8d2aeed8b 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -3,7 +3,7 @@ _If you're using Backstage in your organization, please try to add your company name to this list. This really helps the project to gain momentum and credibility. It's a small contribution back to the project with a big impact._ | Organization | Contact | Description of Use | -| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | | [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | @@ -34,9 +34,9 @@ _If you're using Backstage in your organization, please try to add your company | [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | | [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | | [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | | [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com), [Kamil Wolny](https://github.com/mrwolny) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com), [Kamil Wolny](https://github.com/mrwolny) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | | [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | | [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | | [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | @@ -161,7 +161,7 @@ _If you're using Backstage in your organization, please try to add your company | [OVHcloud](https://www.ovhcloud.com/fr/) | [Jean-Philippe Blary](https://github.com/blaryjp), [Arnaud Bauer](mailto:arnaud.bauer@ovhcloud.com), [Flavien Chantelot](https://github.com/Dorn-) | We're providing Backstage to our collaborators to ease their daily jobs, and let them extends it using plugins. | | [Procter & Gamble](https://us.pg.com/) | [Binita Nayak](https://github.com/binitan), [Josh Rose](https://github.com/joshuarose), [RJ Winkler](https://github.com/rjwink) | P&G leverages Backstage to build internal developer portal to ensure developers' happiness. This developer portal shall act as single source of information needed by development teams to seamlessly create, find and maintain their software components/resources/documentation. | | [SANS Institute](https://www.sans.org) | [Christopher Klewin](mailto:cklewin@sans.org) | Developer portal for centralized visibility, reporting, and tooling across multiple organizations. | -| [Okay](https://www.okayhq.com/) | [Tomas Barreto](mailto:tomas@okayhq.com) | Service catalog, developer portal, and technical documentation | +| [Okay](https://www.okayhq.com/) | [Tomas Barreto](mailto:tomas@okayhq.com) | Service catalog, developer portal, and technical documentation | | [Kaluza](https://www.kaluza.com) | [James Condren](mailto:james.condron@kaluza.com) | To provide an automated golden path to developers, with a focus on discovery and documentation | | [LinkedIn](https://linkedin.com) | [Joshua Lawrence](mailto:jlawrence@linkedin.com) | We are building a platform for internal web tools | | [Forto](https://forto.com) | [Rodolfo Matos](mailto:rodolfo.matos@forto.com) | Still in a experimental phase/assessing the organisational fit. We will be using it mostly a developer portal -- pretty standard use case. | @@ -171,16 +171,16 @@ _If you're using Backstage in your organization, please try to add your company | [AEB](https://www.aeb.com/) | [David Fankhänel](mailto:dfl@aeb.com) | Central developer platform for creating new apps via templates, getting an overview via software catalog, etc | | [SALTO Systems](https://saltosystems.com) | [Ian Cowley](mailto:i.cowley@saltosystems.com) | Currently using Backstage as an internal documentation portal. | | [Lummo](https://lummo.com) | [Anjul Sahu](mailto:anjul@lummo.com) | We are building the internal developer portal using Backstage and bringing up all integrations and service information at one place. | -| [Frontside](https://frontside.com) | [Taras Mankovski](mailto:taras@frontside.com) | +| [Frontside](https://frontside.com) | [Taras Mankovski](mailto:taras@frontside.com) | | | [Stepstone](https://www.stepstone.com/en/) | [Neil Kennedy](mailto:neil.kennedy@stepstone.com) | StepStone is using Backstage to solve problems around ownership and visibility of our applications. We have thousands of repos, multiple legacy systems and a growing platform that is hard to maintain. Backstage is forming the centre of our push to embrace the chaos. | | [idwall](https://idwall.co) | [Rodrigo Catão Araujo](mailto:rodrigo@idwall.co) | Developer Portal for internal engineers to access service catalog, documentation, observability, infrastructure and internal tooling. | | [Jaguar Land Rover](https://www.jaguarlandrover.com) | [Josh Walker](mailto:jwalke18@jaguarlandrover.com) | Users can request a Gitlab user, which creates a commit with the Terraform code. | -| [Glovo](http://glovoapp.com/) | [Yaser Toutah](mailto:yaser.toutah@glovoapp.com) | Developer Portal to improve our Developer Experience, identify ownership and track metadata for our services and tools. It's our Service Catalog. In addition to that, we use it for Service Creation, and much more. | +| [Glovo](http://glovoapp.com/) | [Yaser Toutah](mailto:yaser.toutah@glovoapp.com) | Developer Portal to improve our Developer Experience, identify ownership and track metadata for our services and tools. It's our Service Catalog. In addition to that, we use it for Service Creation, and much more. | | [Dixa](https://dixa.com) | [Jens Møller](mailto:jsc@dixa.com) | We are in early stages, but using it to get overview of our repositories and ownership of these. We want among many things to use it for compliance and easier access to key metrics for our repos. | | [Notino](https://notino.com) | [Jan Remunda](mailto:jan.remunda@notino.com) | Backstage is our developer portal. We use it as service catalog and for technical documentation. | | [Polarpoint](https://polarpoint.io/) | [Surj Bains](https://github.com/polarpoint-io) | We are using Backstage as our Developer portal as well as for hosting our DevOps portal for software catalog. | | [Niche](https://niche.com) | [Zach Romitz](mailto:zach.romitz@niche.com) | We are using the Software Catalog, Software Templates, API documentation, and Techdocs to try and centralize service information. | -| [Mercedes-Benz.io](https://www.mercedes-benz.io/) | [Manuel Santos](https://github.com/manusant) | At Mercedes-Benz we use it as a developer portal with software catalog, TechDocs, Scaffolding and custom plugins. It provides an overview of our tech ecosystem to our product development teams. The portal also serves as a way to foster collaboration among the numerous companies of the Mercedes-Benz Group. -| [Funding Circle](https://www.fundingcircle.com/) | [Ariel Pacciaroni](https://github.com/arielpacciaroni) | We are building the internal developer portal using Backstage project and centralizing all services information at one place. The portal helps us track down repositories ownership as well as direct access to key information on every component. -| [Clarivate](https://www.clarivate.com) | [Gabriele Carteni](mailto:gabriele.carteni@clarivate.com) | We are building our Developer Portal using Backstage to have a better control over our software ecosystem, integrate SDLC tools and promote best practices | -| [Cho Tot](https://www.chotot.com) | [Chotot Team](mailto:sre@chotot.vn) | Internal developer portal, service catalog with CI/CD tools. +| [Mercedes-Benz.io](https://www.mercedes-benz.io/) | [Manuel Santos](https://github.com/manusant) | At Mercedes-Benz we use it as a developer portal with software catalog, TechDocs, Scaffolding and custom plugins. It provides an overview of our tech ecosystem to our product development teams. The portal also serves as a way to foster collaboration among the numerous companies of the Mercedes-Benz Group. | +| [Funding Circle](https://www.fundingcircle.com/) | [Ariel Pacciaroni](https://github.com/arielpacciaroni) | We are building the internal developer portal using Backstage project and centralizing all services information at one place. The portal helps us track down repositories ownership as well as direct access to key information on every component. | +| [Clarivate](https://www.clarivate.com) | [Gabriele Carteni](mailto:gabriele.carteni@clarivate.com) | We are building our Developer Portal using Backstage to have a better control over our software ecosystem, integrate SDLC tools and promote best practices | +| [Cho Tot](https://www.chotot.com) | [Chotot Team](mailto:sre@chotot.vn) | Internal developer portal, service catalog with CI/CD tools. | From 40de38154f267faa4f7f7a5c2eeee22166249de4 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 22 Jun 2022 11:45:45 +0200 Subject: [PATCH 40/73] chore: convert to patch Signed-off-by: Johan Haals --- .changeset/two-crews-accept.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/two-crews-accept.md b/.changeset/two-crews-accept.md index deacf1559d..9540f97cb5 100644 --- a/.changeset/two-crews-accept.md +++ b/.changeset/two-crews-accept.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-gitlab': minor +'@backstage/plugin-catalog-backend-module-gitlab': patch --- Add the possibility in the `GitlabDiscoveryEntityProvider` to scan the whole project instead of concrete groups. For that, use a configuration like this one, where the group parameter is omitted (not mandatory anymore): From 32c03c6a69f0d0157af1b80508ded0a8280b85c8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 22 Jun 2022 12:38:37 +0200 Subject: [PATCH 41/73] docs: add node release section to versioning policy Signed-off-by: Patrik Oldsberg --- docs/overview/versioning-policy.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index 7430f3c200..282906c580 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -135,3 +135,21 @@ package export. accessed via `/beta` or `/alpha` imports. - `@alpha` - here be dragons. Not visible in the main package entry point, alpha exports must be accessed via `/alpha` imports. + +## Node.js Releases + +The Backstage project uses [Node.js](https://nodejs.org/) for both its development +tooling and backend runtime. In order for expectations to be clear we use the +following schedule for determining the [Node.js releases](https://nodejs.org/en/about/releases/) that we support: + +- At any given point in time we support exactly two adjacent even-numbered + releases of Node.js, for example v12 and v14. +- Three months before a Node.js release becomes _Active LTS_ we switch support + to that release and the previous one. This is halfway through the _Current LTS_ + cycle for that release and occurs at the end of July every year. + +When we say _Supporting_ a Node.js release, that means the following: + +- The CI pipeline in the main Backstage repo tests towards the supported releases, and we encourage any other Backstage related projects to do the same. +- New Backstage projects created with `@backstage/create-app` will have their `engines.node` version set accordingly. +- Dropping compatibility with unsupported releases is not considered a breaking change. This includes using new syntax or APIs, as well as bumping dependencies that drop support for these versions. From f74361a5850c3d20923553221172ad947dc68352 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Wed, 22 Jun 2022 17:29:58 +0400 Subject: [PATCH 42/73] removed dagsNotFound state Signed-off-by: Daniele.Mazzotta --- .../DagTableComponent/DagTableComponent.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx index ff7b961c48..46127e0b5e 100644 --- a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx +++ b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx @@ -31,7 +31,7 @@ import Tooltip from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; import Alert from '@material-ui/lab/Alert'; -import React, { useState } from 'react'; +import React from 'react'; import useAsync from 'react-use/lib/useAsync'; import { apacheAirflowApiRef } from '../../api'; import { Dag } from '../../api/types'; @@ -136,15 +136,10 @@ type DagTableComponentProps = { export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { const apiClient = useApi(apacheAirflowApiRef); - const [dagsNotFound, setDagsNotFound] = useState(); const { value, loading, error } = useAsync(async (): Promise => { if (dagIds) { - // eslint-disable-next-line @typescript-eslint/no-shadow - const { dags, dagsNotFound } = await apiClient.getDags(dagIds); - if (dagsNotFound.length) { - setDagsNotFound(dagsNotFound); - } + const { dags } = await apiClient.getDags(dagIds); return dags; } return await apiClient.listDags(); @@ -161,13 +156,16 @@ export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { id: el.dag_id, // table records require `id` attribute dagUrl: `${apiClient.baseUrl}dag_details?dag_id=${el.dag_id}`, // construct path to DAG using `baseUrl` })); - + const dagsNotFound = + dagIds && value + ? dagIds.filter(id => !value.find(d => d.dag_id === id)) + : []; return ( <> - {dagsNotFound && ( + {dagsNotFound.length && ( {dagsNotFound.map(dagId => ( - {dagId} + {dagId} ))} )} From fbfbff6bf7ce0bf169c7a03e2f9b8d8bedcc40b3 Mon Sep 17 00:00:00 2001 From: Tomasz Szuba Date: Wed, 22 Jun 2022 16:25:49 +0200 Subject: [PATCH 43/73] Add possibility to resolve relations by RDN, in addition to UUID and DN in LDAP plugin This is required to support memberUid attribute https://ldapwiki.com/wiki/MemberUid Signed-off-by: Tomasz Szuba --- .changeset/twelve-candles-jump.md | 5 + .../src/ldap/read.test.ts | 302 +++++++----------- .../src/ldap/read.ts | 2 + 3 files changed, 119 insertions(+), 190 deletions(-) create mode 100644 .changeset/twelve-candles-jump.md diff --git a/.changeset/twelve-candles-jump.md b/.changeset/twelve-candles-jump.md new file mode 100644 index 0000000000..9d0cccd0ba --- /dev/null +++ b/.changeset/twelve-candles-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Add possibility to resolve relations by RDN, in addition to UUID and DN diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 410ac847b3..49803b9c39 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -345,206 +345,128 @@ describe('readLdapGroups', () => { describe('resolveRelations', () => { describe('lookup', () => { - it('matches by DN', () => { - const parent = group({ - metadata: { - name: 'parent', - annotations: { [LDAP_DN_ANNOTATION]: 'pa' }, - }, - }); - const child = group({ - metadata: { - name: 'child', - annotations: { [LDAP_DN_ANNOTATION]: 'ca' }, - }, - }); - const groupMember = new Map>([ - ['pa', new Set(['ca'])], - ]); - resolveRelations([parent, child], [], new Map(), new Map(), groupMember); - expect(parent.spec.children).toEqual(['group:default/child']); - expect(child.spec.parent).toEqual('group:default/parent'); - }); - - it('matches by UUID', () => { - const parent = group({ - metadata: { - name: 'parent', - annotations: { [LDAP_UUID_ANNOTATION]: 'pa' }, - }, - }); - const child = group({ - metadata: { - name: 'child', - annotations: { [LDAP_UUID_ANNOTATION]: 'ca' }, - }, - }); - const groupMember = new Map>([ - ['pa', new Set(['ca'])], - ]); - resolveRelations([parent, child], [], new Map(), new Map(), groupMember); - expect(parent.spec.children).toEqual(['group:default/child']); - expect(child.spec.parent).toEqual('group:default/parent'); - }); + it.each([LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION])( + 'matches by %s', + annotation => { + const parent = group({ + metadata: { + name: 'parent', + annotations: { [annotation]: 'pa' }, + }, + }); + const child = group({ + metadata: { + name: 'child', + annotations: { [annotation]: 'ca' }, + }, + }); + const groupMember = new Map>([ + ['pa', new Set(['ca'])], + ]); + resolveRelations( + [parent, child], + [], + new Map(), + new Map(), + groupMember, + ); + expect(parent.spec.children).toEqual(['group:default/child']); + expect(child.spec.parent).toEqual('group:default/parent'); + }, + ); }); describe('userMemberOf', () => { - it('populates relations by dn', () => { - const host = group({ - metadata: { name: 'host', annotations: { [LDAP_DN_ANNOTATION]: 'ha' } }, - }); - const member = user({ - metadata: { - name: 'member', - annotations: { [LDAP_DN_ANNOTATION]: 'ma' }, - }, - }); - const userMemberOf = new Map>([ - ['ma', new Set(['ha'])], - ]); - resolveRelations([host], [member], userMemberOf, new Map(), new Map()); - expect(member.spec.memberOf).toEqual(['group:default/host']); - }); - - it('populates relations by uuid', () => { - const host = group({ - metadata: { - name: 'host', - annotations: { [LDAP_UUID_ANNOTATION]: 'ha' }, - }, - }); - const member = user({ - metadata: { - name: 'member', - annotations: { [LDAP_DN_ANNOTATION]: 'ma' }, - }, - }); - const userMemberOf = new Map>([ - ['ma', new Set(['ha'])], - ]); - resolveRelations([host], [member], userMemberOf, new Map(), new Map()); - expect(member.spec.memberOf).toEqual(['group:default/host']); - }); + it.each([LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION])( + 'populates relations by %s', + annotation => { + const host = group({ + metadata: { name: 'host', annotations: { [annotation]: 'ha' } }, + }); + const member = user({ + metadata: { + name: 'member', + annotations: { [annotation]: 'ma' }, + }, + }); + const userMemberOf = new Map>([ + ['ma', new Set(['ha'])], + ]); + resolveRelations([host], [member], userMemberOf, new Map(), new Map()); + expect(member.spec.memberOf).toEqual(['group:default/host']); + }, + ); }); describe('groupMemberOf', () => { - it('populates relations by dn', () => { - const parent = group({ - metadata: { - name: 'parent', - annotations: { [LDAP_DN_ANNOTATION]: 'pa' }, - }, - }); - const child = group({ - metadata: { - name: 'child', - annotations: { [LDAP_DN_ANNOTATION]: 'ca' }, - }, - }); - const groupMemberOf = new Map>([ - ['ca', new Set(['pa'])], - ]); - resolveRelations( - [parent, child], - [], - new Map(), - groupMemberOf, - new Map(), - ); - expect(parent.spec.children).toEqual(['group:default/child']); - expect(child.spec.parent).toEqual('group:default/parent'); - }); - }); - - it('populates relations by uuid', () => { - const parent = group({ - metadata: { - name: 'parent', - annotations: { [LDAP_UUID_ANNOTATION]: 'pa' }, + it.each([LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION])( + 'populates relations by %s', + annotation => { + const parent = group({ + metadata: { + name: 'parent', + annotations: { [annotation]: 'pa' }, + }, + }); + const child = group({ + metadata: { + name: 'child', + annotations: { [annotation]: 'ca' }, + }, + }); + const groupMemberOf = new Map>([ + ['ca', new Set(['pa'])], + ]); + resolveRelations( + [parent, child], + [], + new Map(), + groupMemberOf, + new Map(), + ); + expect(parent.spec.children).toEqual(['group:default/child']); + expect(child.spec.parent).toEqual('group:default/parent'); }, - }); - const child = group({ - metadata: { - name: 'child', - annotations: { [LDAP_UUID_ANNOTATION]: 'ca' }, - }, - }); - const groupMemberOf = new Map>([ - ['ca', new Set(['pa'])], - ]); - resolveRelations([parent, child], [], new Map(), groupMemberOf, new Map()); - expect(parent.spec.children).toEqual(['group:default/child']); - expect(child.spec.parent).toEqual('group:default/parent'); + ); }); describe('groupMember', () => { - it('populates relations by dn', () => { - const parent = group({ - metadata: { - name: 'parent', - annotations: { [LDAP_DN_ANNOTATION]: 'pa' }, - }, - }); - const child = group({ - metadata: { - name: 'child', - annotations: { [LDAP_DN_ANNOTATION]: 'ca' }, - }, - }); - const member = user({ - metadata: { - name: 'member', - annotations: { [LDAP_DN_ANNOTATION]: 'ma' }, - }, - }); - const groupMember = new Map>([ - ['pa', new Set(['ca', 'ma'])], - ]); - resolveRelations( - [parent, child], - [member], - new Map(), - new Map(), - groupMember, - ); - expect(parent.spec.children).toEqual(['group:default/child']); - expect(child.spec.parent).toEqual('group:default/parent'); - expect(member.spec.memberOf).toEqual(['group:default/parent']); - }); - - it('populates relations by uuid', () => { - const parent = group({ - metadata: { - name: 'parent', - annotations: { [LDAP_UUID_ANNOTATION]: 'pa' }, - }, - }); - const child = group({ - metadata: { - name: 'child', - annotations: { [LDAP_UUID_ANNOTATION]: 'ca' }, - }, - }); - const member = user({ - metadata: { - name: 'member', - annotations: { [LDAP_UUID_ANNOTATION]: 'ma' }, - }, - }); - const groupMember = new Map>([ - ['pa', new Set(['ca', 'ma'])], - ]); - resolveRelations( - [parent, child], - [member], - new Map(), - new Map(), - groupMember, - ); - expect(parent.spec.children).toEqual(['group:default/child']); - expect(child.spec.parent).toEqual('group:default/parent'); - expect(member.spec.memberOf).toEqual(['group:default/parent']); - }); + it.each([LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION])( + 'populates relations by %s', + annotation => { + const parent = group({ + metadata: { + name: 'parent', + annotations: { [annotation]: 'pa' }, + }, + }); + const child = group({ + metadata: { + name: 'child', + annotations: { [annotation]: 'ca' }, + }, + }); + const member = user({ + metadata: { + name: 'member', + annotations: { [annotation]: 'ma' }, + }, + }); + const groupMember = new Map>([ + ['pa', new Set(['ca', 'ma'])], + ]); + resolveRelations( + [parent, child], + [member], + new Map(), + new Map(), + groupMember, + ); + expect(parent.spec.children).toEqual(['group:default/child']); + expect(child.spec.parent).toEqual('group:default/parent'); + expect(member.spec.memberOf).toEqual(['group:default/parent']); + }, + ); }); }); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index d0fe30e0e6..420ba141fc 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -365,11 +365,13 @@ export function resolveRelations( for (const user of users) { userMap.set(stringifyEntityRef(user), user); userMap.set(user.metadata.annotations![LDAP_DN_ANNOTATION], user); + userMap.set(user.metadata.annotations![LDAP_RDN_ANNOTATION], user); userMap.set(user.metadata.annotations![LDAP_UUID_ANNOTATION], user); } for (const group of groups) { groupMap.set(stringifyEntityRef(group), group); groupMap.set(group.metadata.annotations![LDAP_DN_ANNOTATION], group); + groupMap.set(group.metadata.annotations![LDAP_RDN_ANNOTATION], group); groupMap.set(group.metadata.annotations![LDAP_UUID_ANNOTATION], group); } From 166d3a6932aeee3a7b0412349c1bec1a7571c430 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 14:39:32 +0000 Subject: [PATCH 44/73] fix(deps): update dependency @maxim_mazurok/gapi.client.calendar to v3.0.20220617 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 82f4239fee..6b447fa257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4286,9 +4286,9 @@ react-is "^16.8.0 || ^17.0.0" "@maxim_mazurok/gapi.client.calendar@^3.0.20220408": - version "3.0.20220610" - resolved "https://registry.npmjs.org/@maxim_mazurok/gapi.client.calendar/-/gapi.client.calendar-3.0.20220610.tgz#46e0092da54fc8eb1342a3d1af04a071a761faac" - integrity sha512-pZwIaTw+PizFSXrF5WqP4dj+b1Vlj/hNwBY4ocWpJ2uhBDoBPAVIc8lEUNwNZVDbAgtbWHD2Kl84UsP1wmBS+w== + version "3.0.20220617" + resolved "https://registry.npmjs.org/@maxim_mazurok/gapi.client.calendar/-/gapi.client.calendar-3.0.20220617.tgz#140bbfd2caa1770fedbdaec4182eb43684f2d876" + integrity sha512-Vek5Y655GUi4RmUs3cNLfo/KQeReEuvcViJ0KYHl28KmC2Pqmxb4V2YIUhsH7BGVyq84cTIT59qHSirB919Prw== dependencies: "@types/gapi.client" "*" From 86640214f03e7ec48ab124a35f6d25ab3b6ee7cb Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Wed, 22 Jun 2022 15:35:46 +0200 Subject: [PATCH 45/73] Upgrade @rollup/plugin-node-resolve to 13.0.6 Signed-off-by: Julio Zynger --- .changeset/metal-singers-matter.md | 5 +++++ packages/cli/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/metal-singers-matter.md diff --git a/.changeset/metal-singers-matter.md b/.changeset/metal-singers-matter.md new file mode 100644 index 0000000000..2c8d370790 --- /dev/null +++ b/.changeset/metal-singers-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Upgrade `@rollup/plugin-node-resolve` to `^13.0.6` diff --git a/packages/cli/package.json b/packages/cli/package.json index 2cf6f4fe7b..42e8953d1e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,7 +43,7 @@ "@octokit/request": "^5.4.12", "@rollup/plugin-commonjs": "^22.0.0", "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^13.0.0", + "@rollup/plugin-node-resolve": "^13.0.6", "@rollup/plugin-yaml": "^3.1.0", "@spotify/eslint-config-base": "^13.0.0", "@spotify/eslint-config-react": "^13.0.0", From afb57872914b293758b545715739a8a86f58a33a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 15:47:22 +0000 Subject: [PATCH 46/73] fix(deps): update dependency keyv to v4.3.2 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6b447fa257..94120932df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16865,9 +16865,9 @@ keyv@^3.0.0: json-buffer "3.0.0" keyv@^4.0.0, keyv@^4.0.3: - version "4.3.1" - resolved "https://registry.npmjs.org/keyv/-/keyv-4.3.1.tgz#7970672f137d987945821b1a07b524ce5a4edd27" - integrity sha512-nwP7AQOxFzELXsNq3zCx/oh81zu4DHWwCE6W9RaeHb7OHO0JpmKS8n801ovVQC7PTsZDWtPA5j1QY+/WWtARYg== + version "4.3.2" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.3.2.tgz#e839df676a0c7ee594c8835e7c1c83742558e5c2" + integrity sha512-kn8WmodVBe12lmHpA6W8OY7SNh6wVR+Z+wZESF4iF5FCazaVXGWOtnbnvX0tMQ1bO+/TmOD9LziuYMvrIIs0xw== dependencies: compress-brotli "^1.3.8" json-buffer "3.0.1" From 606424388245ac0bec4bf7fbbf69b6662328381d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 22 Jun 2022 18:19:37 +0200 Subject: [PATCH 47/73] Add catalog-backend-module-openapi plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Frinnström --- .../.eslintrc.js | 1 + .../catalog-backend-module-openapi/README.md | 27 ++++ .../api-report.md | 36 ++++++ .../package.json | 53 ++++++++ .../src/OpenApiRefProcessor.test.ts | 101 +++++++++++++++ .../src/OpenApiRefProcessor.ts | 94 ++++++++++++++ .../src/index.ts | 16 +++ .../src/lib/bundle.test.ts | 117 ++++++++++++++++++ .../src/lib/bundle.ts | 69 +++++++++++ .../src/lib/index.ts | 16 +++ .../src/setupTests.ts | 17 +++ yarn.lock | 37 +++++- 12 files changed, 582 insertions(+), 2 deletions(-) create mode 100644 plugins/catalog-backend-module-openapi/.eslintrc.js create mode 100644 plugins/catalog-backend-module-openapi/README.md create mode 100644 plugins/catalog-backend-module-openapi/api-report.md create mode 100644 plugins/catalog-backend-module-openapi/package.json create mode 100644 plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts create mode 100644 plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts create mode 100644 plugins/catalog-backend-module-openapi/src/index.ts create mode 100644 plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts create mode 100644 plugins/catalog-backend-module-openapi/src/lib/bundle.ts create mode 100644 plugins/catalog-backend-module-openapi/src/lib/index.ts create mode 100644 plugins/catalog-backend-module-openapi/src/setupTests.ts diff --git a/plugins/catalog-backend-module-openapi/.eslintrc.js b/plugins/catalog-backend-module-openapi/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-openapi/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-openapi/README.md b/plugins/catalog-backend-module-openapi/README.md new file mode 100644 index 0000000000..1a133521c6 --- /dev/null +++ b/plugins/catalog-backend-module-openapi/README.md @@ -0,0 +1,27 @@ +# Catalog Backend Module for OpenAPI specifications + +This is an extension module to the plugin-catalog-backend plugin, providing extensions targeted at OpenAPI specifications. + +With this you can split your OpenAPI definition into multiple files and reference them. They will be bundled, using an UrlReader, during processing and stored as a single specification. + +## Installation + +### Install the package + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-openapi +``` + +### Adding the plugin to your `packages/backend` + +The processor can be added by importing `OpenApiRefProcessor` in `src/plugins/catalog.ts` in your `backend` package and adding the following. + +```ts +builder.addProcessor( + OpenApiRefProcessor.fromConfig(env.config, { + logger: env.logger, + reader: env.reader, + }), +); +``` diff --git a/plugins/catalog-backend-module-openapi/api-report.md b/plugins/catalog-backend-module-openapi/api-report.md new file mode 100644 index 0000000000..92ddd1e3cd --- /dev/null +++ b/plugins/catalog-backend-module-openapi/api-report.md @@ -0,0 +1,36 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-openapi" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; +import { Config } from '@backstage/config'; +import { Entity } from '@backstage/catalog-model'; +import { LocationSpec } from '@backstage/plugin-catalog-backend'; +import { Logger } from 'winston'; +import { ScmIntegrations } from '@backstage/integration'; +import { UrlReader } from '@backstage/backend-common'; + +// @public (undocumented) +export class OpenApiRefProcessor implements CatalogProcessor { + constructor(options: { + integrations: ScmIntegrations; + logger: Logger; + reader: UrlReader; + }); + // (undocumented) + static fromConfig( + config: Config, + options: { + logger: Logger; + reader: UrlReader; + }, + ): OpenApiRefProcessor; + // (undocumented) + getProcessorName(): string; + // (undocumented) + preProcessEntity(entity: Entity, location: LocationSpec): Promise; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json new file mode 100644 index 0000000000..228147809e --- /dev/null +++ b/plugins/catalog-backend-module-openapi/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-openapi", + "description": "A Backstage catalog backend module that helps with OpenAPI specifications", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-openapi" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" + }, + "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", + "@backstage/backend-common": "^0.14.1-next.0", + "@backstage/catalog-model": "^1.1.0-next.0", + "@backstage/config": "^1.0.1", + "@backstage/integration": "^1.2.2-next.0", + "@backstage/plugin-catalog-backend": "^1.2.1-next.0", + "winston": "^3.2.1", + "yaml": "^2.1.1" + }, + "devDependencies": { + "@backstage/backend-test-utils": "^0.1.26-next.0", + "@backstage/cli": "^0.17.3-next.0", + "openapi-types": "^11.0.1" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts new file mode 100644 index 0000000000..8f2cc571c2 --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { LocationSpec } from '@backstage/plugin-catalog-backend'; +import { OpenApiRefProcessor } from './OpenApiRefProcessor'; +import { bundleOpenApiSpecification } from './lib'; + +jest.mock('./lib', () => ({ + bundleOpenApiSpecification: jest.fn(), +})); + +const bundledSpecification = ''; + +describe('OpenApiRefProcessor', () => { + const mockLocation = (): LocationSpec => ({ + type: 'url', + target: `https://github.com/owner/repo/blob/main/catalog-info.yaml`, + }); + + beforeEach(() => { + (bundleOpenApiSpecification as any).mockResolvedValue(bundledSpecification); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('preProcessEntity', () => { + const setupTest = ({ kind = 'API', spec = {} } = {}) => { + const entity = { + kind, + spec: { definition: '', ...spec }, + }; + const config = new ConfigReader({}); + const reader = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + }; + const processor = OpenApiRefProcessor.fromConfig(config, { + logger: getVoidLogger(), + reader, + }); + + return { entity, processor }; + }; + + it('should bundle OpenAPI specifications', async () => { + const { entity, processor } = setupTest({ + kind: 'API', + spec: { type: 'openapi' }, + }); + + const result = await processor.preProcessEntity( + entity as any, + mockLocation(), + ); + + expect(result.spec?.definition).toEqual(bundledSpecification); + }); + + it('should ignore other kinds', async () => { + const { entity, processor } = setupTest({ kind: 'Group' }); + + const result = await processor.preProcessEntity( + entity as any, + mockLocation(), + ); + + expect(result).toEqual(entity); + }); + + it('should ignore other specification types', async () => { + const { entity, processor } = setupTest({ + kind: 'Group', + spec: { type: 'asyncapi' }, + }); + + const result = await processor.preProcessEntity( + entity as any, + mockLocation(), + ); + + expect(result).toEqual(entity); + }); + }); +}); diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts new file mode 100644 index 0000000000..01875e0779 --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts @@ -0,0 +1,94 @@ +/* + * 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 { UrlReader } from '@backstage/backend-common'; +import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { + CatalogProcessor, + LocationSpec, +} from '@backstage/plugin-catalog-backend'; +import { bundleOpenApiSpecification } from './lib'; +import { Logger } from 'winston'; + +/** @public */ +export class OpenApiRefProcessor implements CatalogProcessor { + private readonly integrations: ScmIntegrations; + private readonly logger: Logger; + private readonly reader: UrlReader; + + static fromConfig( + config: Config, + options: { logger: Logger; reader: UrlReader }, + ) { + const integrations = ScmIntegrations.fromConfig(config); + + return new OpenApiRefProcessor({ + ...options, + integrations, + }); + } + + constructor(options: { + integrations: ScmIntegrations; + logger: Logger; + reader: UrlReader; + }) { + this.integrations = options.integrations; + this.logger = options.logger; + this.reader = options.reader; + } + + getProcessorName(): string { + return 'OpenApiRefProcessor'; + } + + async preProcessEntity( + entity: Entity, + location: LocationSpec, + ): Promise { + if ( + !entity || + entity.kind !== 'API' || + (entity.spec && entity.spec.type !== 'openapi') + ) { + return entity; + } + + const scmIntegration = this.integrations.byUrl(location.target); + if (!scmIntegration) { + return entity; + } + + this.logger.debug(`Bundling OpenAPI specification from ${location.target}`); + try { + const bundledSpec = await bundleOpenApiSpecification( + entity.spec!.definition?.toString(), + location.target, + this.reader, + scmIntegration, + ); + + return { + ...entity, + spec: { ...entity.spec, definition: bundledSpec }, + }; + } catch (error) { + this.logger.error(`Unable to bundle OpenAPI specification`, error); + return entity; + } + } +} diff --git a/plugins/catalog-backend-module-openapi/src/index.ts b/plugins/catalog-backend-module-openapi/src/index.ts new file mode 100644 index 0000000000..f98bee6bac --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { OpenApiRefProcessor } from './OpenApiRefProcessor'; diff --git a/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts b/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts new file mode 100644 index 0000000000..4505f158b5 --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { bundleOpenApiSpecification } from './bundle'; + +const specification = ` +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + $ref: "./paths/pets/list.yaml" +`; + +const list = ` +--- +summary: List all pets +operationId: listPets +tags: + - pets +responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + type: string +`; + +const expectedResult = ` +openapi: 3.0.0 +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + responses: + "200": + description: A paged array of pets + content: + application/json: + schema: + type: string +`; + +describe('bundleOpenApiSpecification', () => { + const readUrl = jest.fn(); + const reader = { + readUrl, + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + }; + + const scmIntegration = ScmIntegrations.fromConfig(new ConfigReader({})).byUrl( + 'https://github.com/owner/repo/blob/main/openapi.yaml', + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return undefined if no specification is supplied', async () => { + expect( + await bundleOpenApiSpecification( + undefined, + 'https://github.com/owner/repo/blob/main/openapi.yaml', + reader, + scmIntegration as any, + ), + ).toBeUndefined(); + }); + + it('should return the bundled specification', async () => { + readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(list), + }); + + const result = await bundleOpenApiSpecification( + specification, + 'https://github.com/owner/repo/blob/main/openapi.yaml', + reader, + scmIntegration as any, + ); + + expect(result).toEqual(expectedResult.trimStart()); + }); +}); diff --git a/plugins/catalog-backend-module-openapi/src/lib/bundle.ts b/plugins/catalog-backend-module-openapi/src/lib/bundle.ts new file mode 100644 index 0000000000..3d35429d8b --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/lib/bundle.ts @@ -0,0 +1,69 @@ +/* + * 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 { UrlReader } from '@backstage/backend-common'; +import { ScmIntegration } from '@backstage/integration'; +import SwaggerParser from '@apidevtools/swagger-parser'; +import { parse, stringify } from 'yaml'; +import * as path from 'path'; + +const protocolPattern = /^(\w{2,}):\/\//i; +const getProtocol = (refPath: string) => { + const match = protocolPattern.exec(refPath); + if (match) { + return match[1].toLowerCase(); + } + return undefined; +}; + +export async function bundleOpenApiSpecification( + specification: string | undefined, + targetUrl: string, + reader: UrlReader, + scmIntegration: ScmIntegration, +): Promise { + const fileUrlReaderResolver: SwaggerParser.ResolverOptions = { + canRead: file => { + const protocol = getProtocol(file.url); + return protocol === undefined || protocol === 'file'; + }, + read: async file => { + const relativePath = path.relative('.', file.url); + const url = scmIntegration.resolveUrl({ + base: targetUrl, + url: relativePath, + }); + if (reader.readUrl) { + const data = await reader.readUrl(url); + return data.buffer(); + } + throw new Error('UrlReader has no readUrl method defined'); + }, + }; + + if (!specification) { + return undefined; + } + + const options: SwaggerParser.Options = { + resolve: { + file: fileUrlReaderResolver, + http: true, + }, + }; + const specObject = parse(specification); + const bundledSpec = await SwaggerParser.bundle(specObject, options); + return stringify(bundledSpec); +} diff --git a/plugins/catalog-backend-module-openapi/src/lib/index.ts b/plugins/catalog-backend-module-openapi/src/lib/index.ts new file mode 100644 index 0000000000..805569181a --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/lib/index.ts @@ -0,0 +1,16 @@ +/* + * 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 * from './bundle'; diff --git a/plugins/catalog-backend-module-openapi/src/setupTests.ts b/plugins/catalog-backend-module-openapi/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/yarn.lock b/yarn.lock index 6b447fa257..8b6112a39a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,7 +16,7 @@ dependencies: "@jridgewell/trace-mapping" "^0.3.0" -"@apidevtools/json-schema-ref-parser@^9.0.6": +"@apidevtools/json-schema-ref-parser@9.0.6", "@apidevtools/json-schema-ref-parser@^9.0.6": version "9.0.6" resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== @@ -25,6 +25,29 @@ call-me-maybe "^1.0.1" js-yaml "^3.13.1" +"@apidevtools/openapi-schemas@^2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@^10.1.0": + version "10.1.0" + resolved "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz#a987d71e5be61feb623203be0c96e5985b192ab6" + integrity sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw== + dependencies: + "@apidevtools/json-schema-ref-parser" "9.0.6" + "@apidevtools/openapi-schemas" "^2.1.0" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + ajv "^8.6.3" + ajv-draft-04 "^1.0.0" + call-me-maybe "^1.0.1" + "@apollo/protobufjs@1.2.2": version "1.2.2" resolved "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz#4bd92cd7701ccaef6d517cdb75af2755f049f87c" @@ -7613,6 +7636,11 @@ aggregate-error@^3.0.0, aggregate-error@^3.1.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv-draft-04@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" + integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -7642,7 +7670,7 @@ ajv@^6.10.0, ajv@^6.10.1, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.10.0, ajv@^8.8.0: +ajv@^8.0.0, ajv@^8.10.0, ajv@^8.6.3, ajv@^8.8.0: version "8.11.0" resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== @@ -19627,6 +19655,11 @@ openapi-sampler@^1.2.1: "@types/json-schema" "^7.0.7" json-pointer "0.6.2" +openapi-types@^11.0.1: + version "11.1.0" + resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-11.1.0.tgz#037969f3dfa5999423ee33bf889fb0d12984277e" + integrity sha512-ZW+Jf12flFF6DXSij8DGL3svDA4RtSyHXjC/xB/JAh18gg3uVfVIFLvCfScUMowrpvlkxsMMbErakbth2g3/iQ== + openid-client@^4.1.1: version "4.9.0" resolved "https://registry.npmjs.org/openid-client/-/openid-client-4.9.0.tgz#bdfc9194435316df419f759ce177635146b43074" From 67503d159ea09572bf89a119e280f5b32d226691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Wed, 22 Jun 2022 18:31:20 +0200 Subject: [PATCH 48/73] Add changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Frinnström --- .changeset/shy-cameras-develop.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/shy-cameras-develop.md diff --git a/.changeset/shy-cameras-develop.md b/.changeset/shy-cameras-develop.md new file mode 100644 index 0000000000..c62a9a56e3 --- /dev/null +++ b/.changeset/shy-cameras-develop.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend-module-openapi': minor +--- + +Add basic OpenAPI \$ref support. + +For more information see [here](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-openapi). From 3524900dfd8a301c1240c0cf69be694b556d8568 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:57:48 +0000 Subject: [PATCH 49/73] fix(deps): update dependency run-script-webpack-plugin to v0.1.1 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 94120932df..9b93d7945b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22776,9 +22776,9 @@ run-parallel@^1.1.9: integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== run-script-webpack-plugin@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/run-script-webpack-plugin/-/run-script-webpack-plugin-0.1.0.tgz#6250a4734511836628259666d56641a27526875d" - integrity sha512-PKddVrnTu1BO90ogvN93yP/zg3X8Q4XvSKKyGVZFye9VBVbgVkFllKykNOomi9IfNtnoFFzu6TkLONIuXq5URA== + version "0.1.1" + resolved "https://registry.npmjs.org/run-script-webpack-plugin/-/run-script-webpack-plugin-0.1.1.tgz#dad3114be32eb864d2160306e4d9c52a2c1cfd59" + integrity sha512-PrxBRLv1K9itDKMlootSCyGhdTU+KbKGJ2wF6/k0eyo6M0YGPC58HYbS/J/QsDiwM0t7G99WcuCqto0J7omOXA== rxjs@7.5.5, rxjs@^7.0.0, rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.5.1, rxjs@^7.5.5: version "7.5.5" From 64c7abbb160b2e7497ad5aa98ce31e7070207328 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 22 Jun 2022 14:03:13 -0400 Subject: [PATCH 50/73] Added missing logos Signed-off-by: Taras Mankovski --- microsite/data/plugins/gke-usage.yaml | 2 +- microsite/data/plugins/harbor.yaml | 2 +- microsite/data/plugins/humanitec.yaml | 2 +- microsite/static/img/gke-logo.png | Bin 0 -> 10452 bytes microsite/static/img/harbor-logo.png | Bin 0 -> 18741 bytes 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 microsite/static/img/gke-logo.png create mode 100644 microsite/static/img/harbor-logo.png diff --git a/microsite/data/plugins/gke-usage.yaml b/microsite/data/plugins/gke-usage.yaml index e092f84973..9668b18fdf 100644 --- a/microsite/data/plugins/gke-usage.yaml +++ b/microsite/data/plugins/gke-usage.yaml @@ -5,7 +5,7 @@ authorUrl: https://bestsellerit.com category: Discovery description: This plugin will show you the cost and resource usage of your application within Google Kubernetes Engine (GKE). documentation: https://github.com/BESTSELLER/backstage-plugin-gkeusage/blob/master/README.md -iconUrl: https://bestsellerit.com/img/google-container-engine_avatar.svg +iconUrl: img/gke-logo.png npmPackageName: '@bestsellerit/backstage-plugin-gkeusage' tags: - gke diff --git a/microsite/data/plugins/harbor.yaml b/microsite/data/plugins/harbor.yaml index 9023e0c434..64b2033021 100644 --- a/microsite/data/plugins/harbor.yaml +++ b/microsite/data/plugins/harbor.yaml @@ -5,7 +5,7 @@ authorUrl: https://bestsellerit.com category: Discovery description: This plugin will show you information about Docker images within the Harbor cloud native registry. documentation: https://github.com/BESTSELLER/backstage-plugin-harbor/blob/master/README.md -iconUrl: https://bestsellerit.com/img/terraform-harbor/goharbor.jpeg +iconUrl: img/harbor-logo.png npmPackageName: '@bestsellerit/backstage-plugin-harbor' tags: - goharbor diff --git a/microsite/data/plugins/humanitec.yaml b/microsite/data/plugins/humanitec.yaml index 88c0cd0a08..bd5dec9dc6 100644 --- a/microsite/data/plugins/humanitec.yaml +++ b/microsite/data/plugins/humanitec.yaml @@ -7,5 +7,5 @@ description: | Show workloads, environments and resources deployed by Humanitec Platform Orchestrator. Plugin includes an Entity ComponentCard, Backend API route and scaffolder actions. documentation: https://github.com/thefrontside/backstage/tree/main/plugins/humanitec -iconUrl: ../../static/img/humanitec-logo.png +iconUrl: img/humanitec-logo.png npmPackageName: '@frontside/backstage-plugin-humanitec' diff --git a/microsite/static/img/gke-logo.png b/microsite/static/img/gke-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5af1abbf9a831ebe8b9a9ff87d17e78d2789236c GIT binary patch literal 10452 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 literal 0 HcmV?d00001 From fea3933d976081fd3adad8bf7a684e3dce83438a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 02:34:54 +0000 Subject: [PATCH 51/73] chore(deps): update dependency msw to v0.42.3 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9b93d7945b..cf4f33b1d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18805,9 +18805,9 @@ msw@^0.39.2: yargs "^17.3.1" msw@^0.42.0: - version "0.42.1" - resolved "https://registry.npmjs.org/msw/-/msw-0.42.1.tgz#2496d3e191754b68686e2530de459a2e102f85c4" - integrity sha512-LZZuz7VddL45gCBgfBWHyXj6a4W7OTJY0mZPoipJ3P/xwbuJwrtwB3IJrWlqBM8aink/eTKlRxwzmtIAwCj5yQ== + version "0.42.3" + resolved "https://registry.npmjs.org/msw/-/msw-0.42.3.tgz#150c475e2cb6d53c67503bd0e3f6251bfd075328" + integrity sha512-zrKBIGCDsNUCZLd3DLSeUtRruZ0riwJgORg9/bSDw3D0PTI8XUGAK3nC0LJA9g0rChGuKaWK/SwObA8wpFrz4g== dependencies: "@mswjs/cookies" "^0.2.0" "@mswjs/interceptors" "^0.16.3" From 007cba9eb12304057f3663ec48de6754c7ab7414 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Thu, 23 Jun 2022 10:07:08 +0400 Subject: [PATCH 52/73] fixed issue where error message would be 0 instead of empty element Signed-off-by: Daniele.Mazzotta --- .../src/components/DagTableComponent/DagTableComponent.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx index 46127e0b5e..2514d70d45 100644 --- a/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx +++ b/plugins/apache-airflow/src/components/DagTableComponent/DagTableComponent.tsx @@ -162,12 +162,14 @@ export const DagTableComponent = ({ dagIds }: DagTableComponentProps) => { : []; return ( <> - {dagsNotFound.length && ( + {dagsNotFound.length ? ( {dagsNotFound.map(dagId => ( {dagId} ))} + ) : ( + '' )} From aed6b691cb8c8f2b85b3edf999f6b57b39e66b58 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Thu, 23 Jun 2022 10:29:10 +0400 Subject: [PATCH 53/73] fixed failing test while fetching getDags Signed-off-by: Daniele.Mazzotta --- .../apache-airflow/src/api/ApacheAirflowClient.test.ts | 9 +++++++++ plugins/apache-airflow/src/api/ApacheAirflowClient.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts b/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts index 329f89ee52..b9bdb02283 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowClient.test.ts @@ -107,6 +107,15 @@ describe('ApacheAirflowClient', () => { ); }), + rest.get(`${mockBaseUrl}/airflow/dags/:dag_id`, (req, res, ctx) => { + const { dag_id } = req.params; + const dag = dags.find(d => d.dag_id === dag_id); + if (dag) { + return res(ctx.json(dag)); + } + return res(ctx.status(404)); + }), + rest.patch(`${mockBaseUrl}/airflow/dags/:dag_id`, (req, res, ctx) => { const { dag_id } = req.params; const body = JSON.parse(req.body as string); diff --git a/plugins/apache-airflow/src/api/ApacheAirflowClient.ts b/plugins/apache-airflow/src/api/ApacheAirflowClient.ts index bf095a96ba..b6c0a09c3a 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowClient.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowClient.ts @@ -82,7 +82,7 @@ export class ApacheAirflowClient implements ApacheAirflowApi { const response = await Promise.all( dagIds.map(id => { return this.fetch(`/dags/${id}`).catch(e => { - if (e.message === 'NOT FOUND') { + if (e.message === 'Not Found') { dagsNotFound.push(id); } else { throw e; From 7bc7b1d37617b5de113547979c224f27a10f3ad8 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Thu, 23 Jun 2022 10:35:50 +0400 Subject: [PATCH 54/73] case-insensitive NOT FOUND error catching in getDags Signed-off-by: Daniele.Mazzotta --- plugins/apache-airflow/src/api/ApacheAirflowClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/apache-airflow/src/api/ApacheAirflowClient.ts b/plugins/apache-airflow/src/api/ApacheAirflowClient.ts index b6c0a09c3a..c722f5977d 100644 --- a/plugins/apache-airflow/src/api/ApacheAirflowClient.ts +++ b/plugins/apache-airflow/src/api/ApacheAirflowClient.ts @@ -82,7 +82,7 @@ export class ApacheAirflowClient implements ApacheAirflowApi { const response = await Promise.all( dagIds.map(id => { return this.fetch(`/dags/${id}`).catch(e => { - if (e.message === 'Not Found') { + if (e.message.toUpperCase('en-US') === 'NOT FOUND') { dagsNotFound.push(id); } else { throw e; From 9592a6cefd7acd7e7917fa2e4c2fb16c617981b8 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 23 Jun 2022 10:54:40 +0200 Subject: [PATCH 55/73] fix(search): race condition on search bar test Signed-off-by: Camila Belo --- .../components/SearchBar/SearchBar.test.tsx | 337 ++++++++++-------- 1 file changed, 183 insertions(+), 154 deletions(-) diff --git a/plugins/search/src/components/SearchBar/SearchBar.test.tsx b/plugins/search/src/components/SearchBar/SearchBar.test.tsx index c48c3d3ed2..4d8a46024c 100644 --- a/plugins/search/src/components/SearchBar/SearchBar.test.tsx +++ b/plugins/search/src/components/SearchBar/SearchBar.test.tsx @@ -18,59 +18,60 @@ import React from 'react'; import { screen, render, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { configApiRef, analyticsApiRef } from '@backstage/core-plugin-api'; -import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; -import { MockAnalyticsApi, TestApiRegistry } from '@backstage/test-utils'; +import { ConfigReader } from '@backstage/core-app-api'; +import { + MockAnalyticsApi, + TestApiProvider, + renderWithEffects, +} from '@backstage/test-utils'; import { SearchContextProvider, searchApiRef, + SearchBar, } from '@backstage/plugin-search-react'; -import { SearchBar } from './SearchBar'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), -})); +const createInitialState = ({ + term = '', + filters = {}, + types = ['*'], + pageCursor = '', +} = {}) => ({ + term, + filters, + types, + pageCursor, +}); describe('SearchBar', () => { - const initialState = { - term: '', - filters: {}, - types: ['*'], - pageCursor: '', - }; + const query = jest.fn().mockResolvedValue({ results: [] }); - const query = jest.fn().mockResolvedValue({}); - const analyticsApiSpy = new MockAnalyticsApi(); - let apiRegistry: TestApiRegistry; + const analyticsApiMock = new MockAnalyticsApi(); - apiRegistry = TestApiRegistry.from( - [ - configApiRef, - new ConfigReader({ - app: { title: 'Mock title' }, - }), - ], - [searchApiRef, { query }], - ); + const configApiMock = new ConfigReader({ + app: { title: 'Mock title' }, + }); - const name = 'Search'; - const term = 'term'; + const searchApiMock = { query }; - afterAll(() => { - jest.resetAllMocks(); + beforeEach(() => { + jest.clearAllMocks(); }); it('Renders without exploding', async () => { - render( - - + await renderWithEffects( + + - , + , ); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); expect( screen.getByPlaceholderText('Search in Mock title'), ).toBeInTheDocument(); @@ -78,59 +79,72 @@ describe('SearchBar', () => { }); it('Renders with custom placeholder', async () => { - render( - - - + const placeholder = 'placeholder'; + + await renderWithEffects( + + + - , - , + , ); await waitFor(() => { - expect( - screen.getByPlaceholderText('This is a custom placeholder'), - ).toBeInTheDocument(); + expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); }); }); it('Renders based on initial search', async () => { + const term = 'term'; + render( - - + + - , - , + , ); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(term); + expect(screen.getByRole('textbox', { name: 'Search' })).toHaveValue(term); }); }); it('Updates term state when text is entered', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); - const defaultDebounceTime = 200; + const user = userEvent.setup({ delay: null }); - render( - - + await renderWithEffects( + + - , - , + , ); - const textbox = screen.getByRole('textbox', { name }); + const textbox = screen.getByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(defaultDebounceTime); + jest.advanceTimersByTime(200); }); await waitFor(() => { @@ -140,26 +154,36 @@ describe('SearchBar', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ term: value }), ); + jest.useRealTimers(); }); it('Clear button clears term state', async () => { - render( - - + const term = 'term'; + + await renderWithEffects( + + - , + , ); + const textbox = screen.getByRole('textbox', { name: 'Search' }); + await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(term); + expect(textbox).toHaveValue(term); }); await userEvent.click(screen.getByRole('button', { name: 'Clear' })); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(''); + expect(textbox).toHaveValue(''); }); expect(query).toHaveBeenLastCalledWith( @@ -168,12 +192,19 @@ describe('SearchBar', () => { }); it('Should not show clear button', async () => { - render( - - + const term = 'term'; + + await renderWithEffects( + + - , + , ); await waitFor(() => { @@ -184,170 +215,168 @@ describe('SearchBar', () => { }); it('Adheres to provided debounceTime', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); - + const user = userEvent.setup({ delay: null }); const debounceTime = 600; - render( - - + await renderWithEffects( + + - , - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); - - const textbox = screen.getByRole('textbox', { name }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); - expect(query).not.toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); + await waitFor(() => { + expect(query).not.toHaveBeenLastCalledWith( + expect.objectContaining({ term: value }), + ); + }); act(() => { jest.advanceTimersByTime(debounceTime); }); - expect(textbox).toHaveValue(value); + + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ term: value }), ); + jest.useRealTimers(); }); it('does not capture analytics event if not enabled in app', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); + const user = userEvent.setup({ delay: null }); - const debounceTime = 600; - - render( - - - + await renderWithEffects( + + + - , - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); - - const textbox = screen.getByRole('textbox', { name }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); - await waitFor(() => expect(textbox).toHaveValue(value)); + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); + + expect(analyticsApiMock.getEvents()).toHaveLength(0); - expect(analyticsApiSpy.getEvents()).toHaveLength(0); jest.useRealTimers(); }); it('captures analytics events if enabled in app', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); + const user = userEvent.setup({ delay: null }); + const types = ['techdocs', 'software-catalog']; - const debounceTime = 600; - - apiRegistry = TestApiRegistry.from( - [analyticsApiRef, analyticsApiSpy], - [ - configApiRef, - new ConfigReader({ - app: { - title: 'Mock title', - analytics: { - ga: { - trackingId: 'xyz123', + await renderWithEffects( + - - + }), + ], + ]} + > + + - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); - const textbox = screen.getByRole('textbox', { name }); - - const value = 'value'; + let value = 'value'; await user.type(textbox, value); - expect(analyticsApiSpy.getEvents()).toHaveLength(0); + expect(analyticsApiMock.getEvents()).toHaveLength(0); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); await waitFor(() => expect(textbox).toHaveValue(value)); - expect(analyticsApiSpy.getEvents()).toHaveLength(1); - expect(analyticsApiSpy.getEvents()[0]).toEqual({ + expect(analyticsApiMock.getEvents()).toHaveLength(1); + expect(analyticsApiMock.getEvents()[0]).toEqual({ action: 'search', context: { - extension: 'App', - pluginId: 'root', + extension: 'SearchBar', + pluginId: 'search', routeRef: 'unknown', - searchTypes: 'software-catalog,techdocs', + searchTypes: types.toString(), }, - subject: 'value', + subject: value, }); await user.clear(textbox); + value = 'new value'; + // make sure new term is captured - await user.type(textbox, 'new value'); + await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); - await waitFor(() => expect(textbox).toHaveValue('new value')); + await waitFor(() => expect(textbox).toHaveValue(value)); - expect(analyticsApiSpy.getEvents()).toHaveLength(2); - expect(analyticsApiSpy.getEvents()[1]).toEqual({ + expect(analyticsApiMock.getEvents()).toHaveLength(2); + expect(analyticsApiMock.getEvents()[1]).toEqual({ action: 'search', context: { - extension: 'App', - pluginId: 'root', + extension: 'SearchBar', + pluginId: 'search', routeRef: 'unknown', - searchTypes: 'software-catalog,techdocs', + searchTypes: types.toString(), }, - subject: 'new value', + subject: value, }); + jest.useRealTimers(); }); }); From 7d9dc8f271c26c964034a3bcbfcf58eef58b3454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Frinnstr=C3=B6m?= Date: Thu, 23 Jun 2022 11:00:50 +0200 Subject: [PATCH 56/73] Change version to 0.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Frinnström --- plugins/catalog-backend-module-openapi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 228147809e..1c99618ff8 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", - "version": "0.1.0", + "version": "0.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", From 5d7a1cca09a1a198dd1eb01a1568bb25f9f7d4ad Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 23 Jun 2022 11:03:12 +0200 Subject: [PATCH 57/73] point to config schema from package.json Signed-off-by: Emma Indal --- plugins/stack-overflow/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 922277c74a..0fe8ae476f 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -55,5 +55,6 @@ "files": [ "dist", "config" - ] + ], + "configSchema": "config.d.ts" } From 52b4f796e3432b373026877fd3dfaca01beaee0a Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 23 Jun 2022 11:06:52 +0200 Subject: [PATCH 58/73] changeset Signed-off-by: Emma Indal --- .changeset/strange-tables-flash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strange-tables-flash.md diff --git a/.changeset/strange-tables-flash.md b/.changeset/strange-tables-flash.md new file mode 100644 index 0000000000..fd13289752 --- /dev/null +++ b/.changeset/strange-tables-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow': patch +--- + +app-config is now picked up properly. From 635cd6e9d2d2e9698594c52bcc960d3c890d6b2c Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 23 Jun 2022 11:11:12 +0200 Subject: [PATCH 59/73] same for backend plugin Signed-off-by: Emma Indal --- .changeset/strange-tables-flash.md | 1 + plugins/stack-overflow-backend/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/strange-tables-flash.md b/.changeset/strange-tables-flash.md index fd13289752..b42069aa64 100644 --- a/.changeset/strange-tables-flash.md +++ b/.changeset/strange-tables-flash.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-stack-overflow': patch +'@backstage/plugin-stack-overflow-backend': patch --- app-config is now picked up properly. diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json index cc41da042e..901d3b12ee 100644 --- a/plugins/stack-overflow-backend/package.json +++ b/plugins/stack-overflow-backend/package.json @@ -42,5 +42,6 @@ "files": [ "dist", "config" - ] + ], + "configSchema": "config.d.ts" } From 6d61b44466c18828858a86bdaa907a9313291273 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 23 Jun 2022 11:13:21 +0200 Subject: [PATCH 60/73] errors: add ConsumedResponse type + use for ResponseError Signed-off-by: Patrik Oldsberg --- .changeset/sharp-numbers-taste.md | 7 +++++ packages/errors/api-report.md | 23 ++++++++++++-- packages/errors/src/errors/ResponseError.ts | 9 ++++-- packages/errors/src/errors/index.ts | 1 + packages/errors/src/errors/types.ts | 31 +++++++++++++++++++ packages/errors/src/serialization/response.ts | 3 +- 6 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 .changeset/sharp-numbers-taste.md create mode 100644 packages/errors/src/errors/types.ts diff --git a/.changeset/sharp-numbers-taste.md b/.changeset/sharp-numbers-taste.md new file mode 100644 index 0000000000..4819aca18f --- /dev/null +++ b/.changeset/sharp-numbers-taste.md @@ -0,0 +1,7 @@ +--- +'@backstage/errors': minor +--- + +The `ResponseError.fromResponse` now accepts a more narrow response type, in order to avoid incompatibilities between different fetch implementations. + +The `response` property of `ResponseError` has also been narrowed to a new `ConsumedResponse` type that omits all the properties for consuming the body of the response. This is not considered a breaking change as it was always an error to try to consume the body of the response. diff --git a/packages/errors/api-report.md b/packages/errors/api-report.md index a01581135b..aab4e9baec 100644 --- a/packages/errors/api-report.md +++ b/packages/errors/api-report.md @@ -14,6 +14,17 @@ export class AuthenticationError extends CustomErrorBase {} // @public export class ConflictError extends CustomErrorBase {} +// @public +export type ConsumedResponse = { + readonly headers: Headers; + readonly ok: boolean; + readonly redirected: boolean; + readonly status: number; + readonly statusText: string; + readonly type: ResponseType; + readonly url: string; +}; + // @public export class CustomErrorBase extends Error { constructor(message?: string, cause?: Error | unknown); @@ -67,15 +78,21 @@ export class NotModifiedError extends CustomErrorBase {} // @public export function parseErrorResponseBody( - response: Response, + response: ConsumedResponse & { + text(): Promise; + }, ): Promise; // @public export class ResponseError extends Error { readonly body: ErrorResponseBody; readonly cause: Error; - static fromResponse(response: Response): Promise; - readonly response: Response; + static fromResponse( + response: ConsumedResponse & { + text(): Promise; + }, + ): Promise; + readonly response: ConsumedResponse; } // @public diff --git a/packages/errors/src/errors/ResponseError.ts b/packages/errors/src/errors/ResponseError.ts index 84c8137bb5..a26891ef6a 100644 --- a/packages/errors/src/errors/ResponseError.ts +++ b/packages/errors/src/errors/ResponseError.ts @@ -19,6 +19,7 @@ import { ErrorResponseBody, parseErrorResponseBody, } from '../serialization/response'; +import { ConsumedResponse } from './types'; /** * An error thrown as the result of a failed server request. @@ -34,7 +35,7 @@ export class ResponseError extends Error { * Note that the body of this response is always consumed. Its parsed form is * in the `body` field. */ - readonly response: Response; + readonly response: ConsumedResponse; /** * The parsed JSON error body, as sent by the server. @@ -59,7 +60,9 @@ export class ResponseError extends Error { * function consumes the body of the response, and assumes that it hasn't * been consumed before. */ - static async fromResponse(response: Response): Promise { + static async fromResponse( + response: ConsumedResponse & { text(): Promise }, + ): Promise { const data = await parseErrorResponseBody(response); const status = data.response.statusCode || response.status; @@ -77,7 +80,7 @@ export class ResponseError extends Error { private constructor(props: { message: string; - response: Response; + response: ConsumedResponse; data: ErrorResponseBody; cause: Error; }) { diff --git a/packages/errors/src/errors/index.ts b/packages/errors/src/errors/index.ts index bc2fb2aa4e..9e1c2408af 100644 --- a/packages/errors/src/errors/index.ts +++ b/packages/errors/src/errors/index.ts @@ -27,3 +27,4 @@ export { } from './common'; export { CustomErrorBase } from './CustomErrorBase'; export { ResponseError } from './ResponseError'; +export type { ConsumedResponse } from './types'; diff --git a/packages/errors/src/errors/types.ts b/packages/errors/src/errors/types.ts new file mode 100644 index 0000000000..a62975571f --- /dev/null +++ b/packages/errors/src/errors/types.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * ConsumedResponse represents a Response that is known to have been consumed. + * The methods and properties used to read the body contents are therefore omitted. + * + * @public + */ +export type ConsumedResponse = { + readonly headers: Headers; + readonly ok: boolean; + readonly redirected: boolean; + readonly status: number; + readonly statusText: string; + readonly type: ResponseType; + readonly url: string; +}; diff --git a/packages/errors/src/serialization/response.ts b/packages/errors/src/serialization/response.ts index 3580220525..60894b243e 100644 --- a/packages/errors/src/serialization/response.ts +++ b/packages/errors/src/serialization/response.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ConsumedResponse } from '../errors/types'; import { SerializedError } from './error'; /** @@ -53,7 +54,7 @@ export type ErrorResponseBody = { * @param response - The response of a failed request */ export async function parseErrorResponseBody( - response: Response, + response: ConsumedResponse & { text(): Promise }, ): Promise { try { const text = await response.text(); From 617e5ae30e1fa399807a06850e7c9a09c84ee4d6 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 23 Jun 2022 11:29:16 +0200 Subject: [PATCH 61/73] update config to be optional Signed-off-by: Emma Indal --- plugins/stack-overflow-backend/config.d.ts | 4 ++-- plugins/stack-overflow/config.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/stack-overflow-backend/config.d.ts b/plugins/stack-overflow-backend/config.d.ts index 7e7211ca33..2c39d61bfb 100644 --- a/plugins/stack-overflow-backend/config.d.ts +++ b/plugins/stack-overflow-backend/config.d.ts @@ -18,11 +18,11 @@ export interface Config { /** * Configuration options for the stack overflow plugin */ - stackoverflow: { + stackoverflow?: { /** * The base url of the Stack Overflow API used for the plugin * @visibility backend */ - baseUrl: string; + baseUrl?: string; }; } diff --git a/plugins/stack-overflow/config.d.ts b/plugins/stack-overflow/config.d.ts index 811893231e..c0638e3c19 100644 --- a/plugins/stack-overflow/config.d.ts +++ b/plugins/stack-overflow/config.d.ts @@ -18,11 +18,11 @@ export interface Config { /** * Configuration options for the stack overflow plugin */ - stackoverflow: { + stackoverflow?: { /** * The base url of the Stack Overflow API used for the plugin * @visibility frontend */ - baseUrl: string; + baseUrl?: string; }; } From d5b97da41e46243bfd91a4d95888e4009805a8b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 09:54:36 +0000 Subject: [PATCH 62/73] fix(deps): update dependency @codemirror/view to v6.0.2 Signed-off-by: Renovate Bot --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4a6ac9daf7..c1eaefe398 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1985,9 +1985,9 @@ "@lezer/highlight" "^1.0.0" "@codemirror/view@^6.0.0": - version "6.0.1" - resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.0.1.tgz#f56327a5cd241eff5393cfdfbceea897b5266967" - integrity sha512-n5olUr4Ld+XDTZN8+cnUHzkqJPeO2kr55HvBQ+4C1xWlDaRGrsedAoxCgeuGZsXRTUup/H/0e2kAN+a0R3skdA== + version "6.0.2" + resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz#27f4d08edd10a3678cf15390b4fba5e2a7220873" + integrity sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q== dependencies: "@codemirror/state" "^6.0.0" style-mod "^4.0.0" @@ -5342,7 +5342,7 @@ dependencies: "@rollup/pluginutils" "^3.0.8" -"@rollup/plugin-node-resolve@^13.0.0": +"@rollup/plugin-node-resolve@^13.0.6": version "13.3.0" resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== From b9822464b37c266ba2785000ef1e8b45cfb6e6d2 Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Thu, 23 Jun 2022 14:56:37 +0400 Subject: [PATCH 63/73] fixed line wraps Signed-off-by: Daniele.Mazzotta --- plugins/apache-airflow/README.md | 34 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/plugins/apache-airflow/README.md b/plugins/apache-airflow/README.md index 11fce89e4c..305a94f081 100644 --- a/plugins/apache-airflow/README.md +++ b/plugins/apache-airflow/README.md @@ -3,17 +3,14 @@ Welcome to the apache-airflow plugin! This plugin serves as frontend to the REST API exposed by Apache Airflow. -Note only [Airflow v2 (and later)](https://airflow.apache.org/docs/apache-airflow/stable/deprecated-rest-api-ref.html) -integrate with the plugin. +Note only [Airflow v2 (and later)](https://airflow.apache.org/docs/apache-airflow/stable/deprecated-rest-api-ref.html) integrate with the plugin. ## Feature Requests & Ideas - [ ] Add support for running multiple instances of Airflow for monitoring - various deployment stages or business - domains. ([Suggested by @JGoldman110](https://github.com/backstage/backstage/issues/735#issuecomment-985063468)) + various deployment stages or business domains. ([Suggested by @JGoldman110](https://github.com/backstage/backstage/issues/735#issuecomment-985063468)) - [ ] Make owner chips in the DAG table clickable, resolving to a user or group - in the entity - catalog. ([Suggested by @julioz](https://github.com/backstage/backstage/pull/8348#discussion_r764766295)) + in the entity catalog. ([Suggested by @julioz](https://github.com/backstage/backstage/pull/8348#discussion_r764766295)) ## Installation @@ -47,22 +44,21 @@ yarn --cwd packages/app add @backstage/plugin-apache-airflow If you just want to embed the DAGs into an existing page, you can use the `ApacheAirflowDagTable` -```typescript -import {ApacheAirflowDagTable} from '@backstage/plugin-apache-airflow'; +```tsx +import { ApacheAirflowDagTable } from '@backstage/plugin-apache-airflow'; export function SomeEntityPage(): JSX.Element { return ( - - - < /Grid> -) - ; + + + + ); } ``` From 707bb20343306ad7c046d0916408ab9df149f51b Mon Sep 17 00:00:00 2001 From: "Daniele.Mazzotta" Date: Thu, 23 Jun 2022 14:57:21 +0400 Subject: [PATCH 64/73] set changeset to minor instead of patch Signed-off-by: Daniele.Mazzotta --- .changeset/lemon-goats-obey.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/lemon-goats-obey.md b/.changeset/lemon-goats-obey.md index 2c45c67282..7bdb59bd61 100644 --- a/.changeset/lemon-goats-obey.md +++ b/.changeset/lemon-goats-obey.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-apache-airflow': patch +'@backstage/plugin-apache-airflow': minor --- Exposed DagTableComponent as standalone component + added a prop to get only select DAGs instead of the full list From bb48de7aa83f206df2b81f4b2dec2294467832bd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 23 Jun 2022 13:06:55 +0200 Subject: [PATCH 65/73] example-todo-list: fix package roles Signed-off-by: Patrik Oldsberg --- plugins/example-todo-list-backend/package.json | 3 +++ plugins/example-todo-list-common/package.json | 2 +- plugins/example-todo-list/package.json | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index b27b050a50..de8a1638ad 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -5,6 +5,9 @@ "types": "src/index.ts", "license": "Apache-2.0", "private": true, + "backstage": { + "role": "backend-plugin" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 1e90afb13a..fce0a05714 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -11,7 +11,7 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "frontend-plugin" + "role": "common-library" }, "scripts": { "start": "backstage-cli package start", diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 4c5fb0c83e..7764d7c2cb 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -5,6 +5,9 @@ "types": "src/index.ts", "license": "Apache-2.0", "private": true, + "backstage": { + "role": "frontend-plugin" + }, "publishConfig": { "access": "public", "main": "dist/index.esm.js", From 838285259755b31921f5b05bf82165c1e74c0ccb Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 23 Jun 2022 12:58:24 +0200 Subject: [PATCH 66/73] refactor(search-react): apply review suggestions Signed-off-by: Camila Belo --- .../components/SearchBar/SearchBar.test.tsx | 328 ++++++++------- .../components/SearchBar/SearchBar.test.tsx | 382 ------------------ 2 files changed, 178 insertions(+), 532 deletions(-) delete mode 100644 plugins/search/src/components/SearchBar/SearchBar.test.tsx diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx index d65278f7ec..fba50f74d8 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx @@ -17,59 +17,59 @@ import React from 'react'; import { screen, render, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; - import { configApiRef, analyticsApiRef } from '@backstage/core-plugin-api'; -import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; -import { MockAnalyticsApi, TestApiRegistry } from '@backstage/test-utils'; - +import { ConfigReader } from '@backstage/core-app-api'; +import { + MockAnalyticsApi, + TestApiProvider, + renderWithEffects, +} from '@backstage/test-utils'; import { searchApiRef } from '../../api'; import { SearchContextProvider } from '../../context'; import { SearchBar } from './SearchBar'; -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), -})); +const createInitialState = ({ + term = '', + filters = {}, + types = ['*'], + pageCursor = '', +} = {}) => ({ + term, + filters, + types, + pageCursor, +}); describe('SearchBar', () => { - const initialState = { - term: '', - filters: {}, - types: ['*'], - pageCursor: '', - }; + const query = jest.fn().mockResolvedValue({ results: [] }); - const query = jest.fn().mockResolvedValue({}); - const analyticsApiSpy = new MockAnalyticsApi(); - let apiRegistry: TestApiRegistry; + const analyticsApiMock = new MockAnalyticsApi(); - apiRegistry = TestApiRegistry.from( - [ - configApiRef, - new ConfigReader({ - app: { title: 'Mock title' }, - }), - ], - [searchApiRef, { query }], - ); + const configApiMock = new ConfigReader({ + app: { title: 'Mock title' }, + }); - const name = 'Search'; - const term = 'term'; + const searchApiMock = { query }; - afterAll(() => { - jest.resetAllMocks(); + beforeEach(() => { + jest.clearAllMocks(); }); it('Renders without exploding', async () => { - render( - - + await renderWithEffects( + + - , + , ); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); expect( screen.getByPlaceholderText('Search in Mock title'), ).toBeInTheDocument(); @@ -77,59 +77,72 @@ describe('SearchBar', () => { }); it('Renders with custom placeholder', async () => { - render( - - - + const placeholder = 'placeholder'; + + await renderWithEffects( + + + - , - , + , ); await waitFor(() => { - expect( - screen.getByPlaceholderText('This is a custom placeholder'), - ).toBeInTheDocument(); + expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); }); }); it('Renders based on initial search', async () => { + const term = 'term'; + render( - - + + - , - , + , ); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(term); + expect(screen.getByRole('textbox', { name: 'Search' })).toHaveValue(term); }); }); it('Updates term state when text is entered', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); - const defaultDebounceTime = 200; + const user = userEvent.setup({ delay: null }); - render( - - + await renderWithEffects( + + - , - , + , ); - const textbox = screen.getByRole('textbox', { name }); + const textbox = screen.getByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(defaultDebounceTime); + jest.advanceTimersByTime(200); }); await waitFor(() => { @@ -139,26 +152,36 @@ describe('SearchBar', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ term: value }), ); + jest.useRealTimers(); }); it('Clear button clears term state', async () => { - render( - - + const term = 'term'; + + await renderWithEffects( + + - , + , ); + const textbox = screen.getByRole('textbox', { name: 'Search' }); + await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(term); + expect(textbox).toHaveValue(term); }); await userEvent.click(screen.getByRole('button', { name: 'Clear' })); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(''); + expect(textbox).toHaveValue(''); }); expect(query).toHaveBeenLastCalledWith( @@ -167,12 +190,19 @@ describe('SearchBar', () => { }); it('Should not show clear button', async () => { - render( - - + const term = 'term'; + + await renderWithEffects( + + - , + , ); await waitFor(() => { @@ -183,170 +213,168 @@ describe('SearchBar', () => { }); it('Adheres to provided debounceTime', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); - + const user = userEvent.setup({ delay: null }); const debounceTime = 600; - render( - - + await renderWithEffects( + + - , - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); - - const textbox = screen.getByRole('textbox', { name }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); - expect(query).not.toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); + await waitFor(() => { + expect(query).not.toHaveBeenLastCalledWith( + expect.objectContaining({ term: value }), + ); + }); act(() => { jest.advanceTimersByTime(debounceTime); }); - expect(textbox).toHaveValue(value); + + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ term: value }), ); + jest.useRealTimers(); }); it('does not capture analytics event if not enabled in app', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); + const user = userEvent.setup({ delay: null }); - const debounceTime = 600; - - render( - - - + await renderWithEffects( + + + - , - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); - - const textbox = screen.getByRole('textbox', { name }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); - await waitFor(() => expect(textbox).toHaveValue(value)); + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); + + expect(analyticsApiMock.getEvents()).toHaveLength(0); - expect(analyticsApiSpy.getEvents()).toHaveLength(0); jest.useRealTimers(); }); it('captures analytics events if enabled in app', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); + const user = userEvent.setup({ delay: null }); + const types = ['techdocs', 'software-catalog']; - const debounceTime = 600; - - apiRegistry = TestApiRegistry.from( - [analyticsApiRef, analyticsApiSpy], - [ - configApiRef, - new ConfigReader({ - app: { - title: 'Mock title', - analytics: { - ga: { - trackingId: 'xyz123', + await renderWithEffects( + - - + }), + ], + ]} + > + + - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); - const textbox = screen.getByRole('textbox', { name }); - - const value = 'value'; + let value = 'value'; await user.type(textbox, value); - expect(analyticsApiSpy.getEvents()).toHaveLength(0); + expect(analyticsApiMock.getEvents()).toHaveLength(0); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); await waitFor(() => expect(textbox).toHaveValue(value)); - expect(analyticsApiSpy.getEvents()).toHaveLength(1); - expect(analyticsApiSpy.getEvents()[0]).toEqual({ + expect(analyticsApiMock.getEvents()).toHaveLength(1); + expect(analyticsApiMock.getEvents()[0]).toEqual({ action: 'search', context: { extension: 'SearchBar', pluginId: 'search', routeRef: 'unknown', - searchTypes: 'software-catalog,techdocs', + searchTypes: types.toString(), }, - subject: 'value', + subject: value, }); await user.clear(textbox); + value = 'new value'; + // make sure new term is captured - await user.type(textbox, 'new value'); + await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); - await waitFor(() => expect(textbox).toHaveValue('new value')); + await waitFor(() => expect(textbox).toHaveValue(value)); - expect(analyticsApiSpy.getEvents()).toHaveLength(2); - expect(analyticsApiSpy.getEvents()[1]).toEqual({ + expect(analyticsApiMock.getEvents()).toHaveLength(2); + expect(analyticsApiMock.getEvents()[1]).toEqual({ action: 'search', context: { extension: 'SearchBar', pluginId: 'search', routeRef: 'unknown', - searchTypes: 'software-catalog,techdocs', + searchTypes: types.toString(), }, - subject: 'new value', + subject: value, }); + jest.useRealTimers(); }); }); diff --git a/plugins/search/src/components/SearchBar/SearchBar.test.tsx b/plugins/search/src/components/SearchBar/SearchBar.test.tsx deleted file mode 100644 index 4d8a46024c..0000000000 --- a/plugins/search/src/components/SearchBar/SearchBar.test.tsx +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { screen, render, waitFor, act } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { configApiRef, analyticsApiRef } from '@backstage/core-plugin-api'; -import { ConfigReader } from '@backstage/core-app-api'; -import { - MockAnalyticsApi, - TestApiProvider, - renderWithEffects, -} from '@backstage/test-utils'; -import { - SearchContextProvider, - searchApiRef, - SearchBar, -} from '@backstage/plugin-search-react'; - -const createInitialState = ({ - term = '', - filters = {}, - types = ['*'], - pageCursor = '', -} = {}) => ({ - term, - filters, - types, - pageCursor, -}); - -describe('SearchBar', () => { - const query = jest.fn().mockResolvedValue({ results: [] }); - - const analyticsApiMock = new MockAnalyticsApi(); - - const configApiMock = new ConfigReader({ - app: { title: 'Mock title' }, - }); - - const searchApiMock = { query }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('Renders without exploding', async () => { - await renderWithEffects( - - - - - , - ); - - await waitFor(() => { - expect( - screen.getByPlaceholderText('Search in Mock title'), - ).toBeInTheDocument(); - }); - }); - - it('Renders with custom placeholder', async () => { - const placeholder = 'placeholder'; - - await renderWithEffects( - - - - - , - ); - - await waitFor(() => { - expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); - }); - }); - - it('Renders based on initial search', async () => { - const term = 'term'; - - render( - - - - - , - ); - - await waitFor(() => { - expect(screen.getByRole('textbox', { name: 'Search' })).toHaveValue(term); - }); - }); - - it('Updates term state when text is entered', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - - await renderWithEffects( - - - - - , - ); - - const textbox = screen.getByRole('textbox', { name: 'Search' }); - - const value = 'value'; - - await user.type(textbox, value); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => { - expect(textbox).toHaveValue(value); - }); - - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); - - jest.useRealTimers(); - }); - - it('Clear button clears term state', async () => { - const term = 'term'; - - await renderWithEffects( - - - - - , - ); - - const textbox = screen.getByRole('textbox', { name: 'Search' }); - - await waitFor(() => { - expect(textbox).toHaveValue(term); - }); - - await userEvent.click(screen.getByRole('button', { name: 'Clear' })); - - await waitFor(() => { - expect(textbox).toHaveValue(''); - }); - - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ term: '' }), - ); - }); - - it('Should not show clear button', async () => { - const term = 'term'; - - await renderWithEffects( - - - - - , - ); - - await waitFor(() => { - expect( - screen.queryByRole('button', { name: 'Clear' }), - ).not.toBeInTheDocument(); - }); - }); - - it('Adheres to provided debounceTime', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - const debounceTime = 600; - - await renderWithEffects( - - - - - , - ); - - const textbox = await screen.findByRole('textbox', { name: 'Search' }); - - const value = 'value'; - - await user.type(textbox, value); - - await waitFor(() => { - expect(query).not.toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); - }); - - act(() => { - jest.advanceTimersByTime(debounceTime); - }); - - await waitFor(() => { - expect(textbox).toHaveValue(value); - }); - - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); - - jest.useRealTimers(); - }); - - it('does not capture analytics event if not enabled in app', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - - await renderWithEffects( - - - - - , - ); - - const textbox = await screen.findByRole('textbox', { name: 'Search' }); - - const value = 'value'; - - await user.type(textbox, value); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => { - expect(textbox).toHaveValue(value); - }); - - expect(analyticsApiMock.getEvents()).toHaveLength(0); - - jest.useRealTimers(); - }); - - it('captures analytics events if enabled in app', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - const types = ['techdocs', 'software-catalog']; - - await renderWithEffects( - - - - - , - ); - - const textbox = await screen.findByRole('textbox', { name: 'Search' }); - - let value = 'value'; - - await user.type(textbox, value); - - expect(analyticsApiMock.getEvents()).toHaveLength(0); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => expect(textbox).toHaveValue(value)); - - expect(analyticsApiMock.getEvents()).toHaveLength(1); - expect(analyticsApiMock.getEvents()[0]).toEqual({ - action: 'search', - context: { - extension: 'SearchBar', - pluginId: 'search', - routeRef: 'unknown', - searchTypes: types.toString(), - }, - subject: value, - }); - - await user.clear(textbox); - - value = 'new value'; - - // make sure new term is captured - await user.type(textbox, value); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => expect(textbox).toHaveValue(value)); - - expect(analyticsApiMock.getEvents()).toHaveLength(2); - expect(analyticsApiMock.getEvents()[1]).toEqual({ - action: 'search', - context: { - extension: 'SearchBar', - pluginId: 'search', - routeRef: 'unknown', - searchTypes: types.toString(), - }, - subject: value, - }); - - jest.useRealTimers(); - }); -}); From a47c1b1407162bdf48906d374eef91a0d4ff2c7b Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 23 Jun 2022 13:29:40 +0200 Subject: [PATCH 67/73] add config.d.ts to files Signed-off-by: Emma Indal --- plugins/stack-overflow-backend/package.json | 2 +- plugins/stack-overflow/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json index 901d3b12ee..083ab18b42 100644 --- a/plugins/stack-overflow-backend/package.json +++ b/plugins/stack-overflow-backend/package.json @@ -41,7 +41,7 @@ }, "files": [ "dist", - "config" + "config.d.ts" ], "configSchema": "config.d.ts" } diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 0fe8ae476f..793e42881d 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -54,7 +54,7 @@ }, "files": [ "dist", - "config" + "config.d.ts" ], "configSchema": "config.d.ts" } From b2dd93f207c4812632aaaa731ad96fd715cb664f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 13 May 2022 15:49:55 +0200 Subject: [PATCH 68/73] delete headings transformations Signed-off-by: Emma Indal --- docs/README.md | 12 ++++++++++++ .../src/reader/transformers/styles/rules/typeset.ts | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index c63a12b589..f323f3bdbe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,15 @@ # Documentation The Backstage documentation is available at https://backstage.io/docs + +# H1 + +## H2 + +### H3 + +#### H4 + +##### H5 + +###### H6 diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts index e24b61da83..63d0f432e3 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts @@ -38,8 +38,8 @@ ${headings.reduce((style, heading) => { const calculate = (value: typeof fontSize) => { let factor: number | string = 1; if (typeof value === 'number') { - // 60% of the size defined because it is too big - factor = (value / 16) * 0.6; + // convert px to rem + factor = value / 16; } if (typeof value === 'string') { factor = value.replace('rem', ''); From 4c09c09102187a9a508976e5a69147fddcd711b3 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 18 May 2022 21:10:45 +0200 Subject: [PATCH 69/73] refactor(techdocs): apply review suggestions Signed-off-by: Camila Belo --- .changeset/strong-lies-explain.md | 5 +++++ docs/README.md | 12 ------------ packages/theme/api-report.md | 1 + packages/theme/src/baseTheme.ts | 10 ++++++++++ packages/theme/src/types.ts | 1 + .../examples/documented-component/docs/index.md | 14 ++++++++++++++ 6 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 .changeset/strong-lies-explain.md diff --git a/.changeset/strong-lies-explain.md b/.changeset/strong-lies-explain.md new file mode 100644 index 0000000000..d9657be6b4 --- /dev/null +++ b/.changeset/strong-lies-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +Adds optional `htmlFontSize` property and also sets typography design tokens for h5 and h6 in base theme. diff --git a/docs/README.md b/docs/README.md index f323f3bdbe..c63a12b589 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,15 +1,3 @@ # Documentation The Backstage documentation is available at https://backstage.io/docs - -# H1 - -## H2 - -### H3 - -#### H4 - -##### H5 - -###### H6 diff --git a/packages/theme/api-report.md b/packages/theme/api-report.md index f650636710..772e868635 100644 --- a/packages/theme/api-report.md +++ b/packages/theme/api-report.md @@ -145,5 +145,6 @@ export type SimpleThemeOptions = { defaultPageTheme: string; pageTheme?: Record; fontFamily?: string; + htmlFontSize?: number; }; ``` diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index da663e3398..5e222c0c3e 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -24,6 +24,7 @@ import { } from './types'; import { pageTheme as defaultPageThemes } from './pageTheme'; +const DEFAULT_HTML_FONT_SIZE = 16; const DEFAULT_FONT_FAMILY = '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif'; @@ -37,6 +38,7 @@ export function createThemeOptions( ): BackstageThemeOptions { const { palette, + htmlFontSize = DEFAULT_HTML_FONT_SIZE, fontFamily = DEFAULT_FONT_FAMILY, defaultPageTheme, pageTheme = defaultPageThemes, @@ -57,9 +59,17 @@ export function createThemeOptions( }, }, typography: { + htmlFontSize, fontFamily, + h6: { + fontWeight: 700, + fontSize: 20, + marginBottom: 2, + }, h5: { fontWeight: 700, + fontSize: 24, + marginBottom: 4, }, h4: { fontWeight: 700, diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 3d1a64c38f..b921154005 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -149,6 +149,7 @@ export type SimpleThemeOptions = { defaultPageTheme: string; pageTheme?: Record; fontFamily?: string; + htmlFontSize?: number; }; /** diff --git a/plugins/techdocs-backend/examples/documented-component/docs/index.md b/plugins/techdocs-backend/examples/documented-component/docs/index.md index 48a903445a..5a3d7508cd 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/index.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/index.md @@ -11,6 +11,20 @@ You can see also: ## Basic Markdown +Headings: + +# h1 + +## h2 + +### h3 + +#### h4 + +##### h5 + +###### h6 + Here is a bulleted list: - Item one From 726577958fde3d66211b140b1c694db8b0f80f49 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 19 May 2022 09:18:06 +0200 Subject: [PATCH 70/73] chore(techdocs): add missing changesets Signed-off-by: Camila Belo --- .changeset/techdocs-eyes-sit.md | 5 +++++ .changeset/techdocs-sheep-talk.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/techdocs-eyes-sit.md create mode 100644 .changeset/techdocs-sheep-talk.md diff --git a/.changeset/techdocs-eyes-sit.md b/.changeset/techdocs-eyes-sit.md new file mode 100644 index 0000000000..a09410a4fb --- /dev/null +++ b/.changeset/techdocs-eyes-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Add sample headings on the documented component homepage. diff --git a/.changeset/techdocs-sheep-talk.md b/.changeset/techdocs-sheep-talk.md new file mode 100644 index 0000000000..b17408d7a4 --- /dev/null +++ b/.changeset/techdocs-sheep-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Remove the 60% factor from the font size calculation of headers to use the exact size defined in BackstageTheme. From 6259fcf98aab28db1c9e9cf9b427fce966a0724c Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 23 Jun 2022 13:44:11 +0200 Subject: [PATCH 71/73] refactor(techdocs): apply review suggestions Signed-off-by: Camila Belo --- .../src/reader/transformers/styles/rules/typeset.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts index 63d0f432e3..6a3b0461ef 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts @@ -16,8 +16,14 @@ import { RuleOptions } from './types'; +type RuleTypography = RuleOptions['theme']['typography']; + +type BackstageTypography = RuleTypography & { + htmlFontSize?: number; +}; + type TypographyHeadings = Pick< - RuleOptions['theme']['typography'], + RuleTypography, 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' >; @@ -33,13 +39,15 @@ export default ({ theme }: RuleOptions) => ` } ${headings.reduce((style, heading) => { + const htmlFontSize = + (theme.typography as BackstageTypography).htmlFontSize ?? 16; const styles = theme.typography[heading]; const { lineHeight, fontFamily, fontWeight, fontSize } = styles; const calculate = (value: typeof fontSize) => { let factor: number | string = 1; if (typeof value === 'number') { // convert px to rem - factor = value / 16; + factor = value / htmlFontSize; } if (typeof value === 'string') { factor = value.replace('rem', ''); From 96a82d979110dd2bc5a1c7439603c4976905e285 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 23 Jun 2022 11:24:13 +0200 Subject: [PATCH 72/73] cli: remove deprecated commands Signed-off-by: Patrik Oldsberg --- .changeset/green-actors-argue.md | 19 ++++ packages/cli/src/commands/app/build.ts | 35 ------- packages/cli/src/commands/app/serve.ts | 92 ------------------ packages/cli/src/commands/backend/build.ts | 26 ----- packages/cli/src/commands/backend/bundle.ts | 72 -------------- packages/cli/src/commands/backend/dev.ts | 36 ------- packages/cli/src/commands/index.ts | 100 -------------------- packages/cli/src/commands/oldBuild.ts | 41 -------- packages/cli/src/commands/plugin/build.ts | 26 ----- packages/cli/src/commands/plugin/serve.ts | 36 ------- 10 files changed, 19 insertions(+), 464 deletions(-) create mode 100644 .changeset/green-actors-argue.md delete mode 100644 packages/cli/src/commands/app/build.ts delete mode 100644 packages/cli/src/commands/app/serve.ts delete mode 100644 packages/cli/src/commands/backend/build.ts delete mode 100644 packages/cli/src/commands/backend/bundle.ts delete mode 100644 packages/cli/src/commands/backend/dev.ts delete mode 100644 packages/cli/src/commands/oldBuild.ts delete mode 100644 packages/cli/src/commands/plugin/build.ts delete mode 100644 packages/cli/src/commands/plugin/serve.ts diff --git a/.changeset/green-actors-argue.md b/.changeset/green-actors-argue.md new file mode 100644 index 0000000000..e21fbb040c --- /dev/null +++ b/.changeset/green-actors-argue.md @@ -0,0 +1,19 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: Removed the following deprecated package commands: + +- `app:build` - Use `package build` instead +- `app:serve` - Use `package start` instead +- `backend:build` - Use `package build` instead +- `backend:bundle` - Use `package build` instead +- `backend:dev` - Use `package start` instead +- `plugin:build` - Use `package build` instead +- `plugin:serve` - Use `package start` instead +- `build` - Use `package build` instead +- `lint` - Use `package lint` instead +- `prepack` - Use `package prepack` instead +- `postpack` - Use `package postpack` instead + +In order to replace these you need to have [migrated to using package roles](https://backstage.io/docs/tutorials/package-role-migration). diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts deleted file mode 100644 index f90052bc83..0000000000 --- a/packages/cli/src/commands/app/build.ts +++ /dev/null @@ -1,35 +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 fs from 'fs-extra'; -import { OptionValues } from 'commander'; -import { buildBundle } from '../../lib/bundler'; -import { getEnvironmentParallelism } from '../../lib/parallel'; -import { loadCliConfig } from '../../lib/config'; -import { paths } from '../../lib/paths'; - -export default async (opts: OptionValues) => { - const { name } = await fs.readJson(paths.resolveTarget('package.json')); - await buildBundle({ - entry: 'src/index', - parallelism: getEnvironmentParallelism(), - statsJsonEnabled: opts.stats, - ...(await loadCliConfig({ - args: opts.config, - fromPackage: name, - })), - }); -}; diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts deleted file mode 100644 index e41c2367da..0000000000 --- a/packages/cli/src/commands/app/serve.ts +++ /dev/null @@ -1,92 +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 fs from 'fs-extra'; -import chalk from 'chalk'; -import uniq from 'lodash/uniq'; -import { OptionValues } from 'commander'; -import { serveBundle } from '../../lib/bundler'; -import { loadCliConfig } from '../../lib/config'; -import { paths } from '../../lib/paths'; -import { Lockfile } from '../../lib/versioning'; -import { forbiddenDuplicatesFilter, includedFilter } from '../versions/lint'; - -export default async (opts: OptionValues) => { - const lockFilePath = paths.resolveTargetRoot('yarn.lock'); - if (fs.existsSync(lockFilePath)) { - try { - const lockfile = await Lockfile.load(lockFilePath); - const result = lockfile.analyze({ - filter: includedFilter, - }); - const problemPackages = [...result.newVersions, ...result.newRanges] - .map(({ name }) => name) - .filter(name => forbiddenDuplicatesFilter(name)); - - if (problemPackages.length > 0) { - console.log( - chalk.yellow( - `⚠️ Some of the following packages may be outdated or have duplicate installations: - - ${uniq(problemPackages).join(', ')} - `, - ), - ); - console.log( - chalk.yellow( - `⚠️ The following command may fix the issue, but it could also be an issue within one of your dependencies: - - yarn backstage-cli versions:check --fix - `, - ), - ); - } - } catch (error) { - console.log( - chalk.yellow( - `⚠️ Unable to parse yarn.lock file properly: - - ${error} - - skipping analyzer for outdated or duplicate installations - `, - ), - ); - } - } else { - console.log( - chalk.yellow( - `⚠️ Unable to find yarn.lock file: - - skipping analyzer for outdated or duplicate installations - `, - ), - ); - } - - const { name } = await fs.readJson(paths.resolveTarget('package.json')); - const waitForExit = await serveBundle({ - entry: 'src/index', - checksEnabled: opts.check, - ...(await loadCliConfig({ - args: opts.config, - fromPackage: name, - withFilteredKeys: true, - })), - }); - - await waitForExit(); -}; diff --git a/packages/cli/src/commands/backend/build.ts b/packages/cli/src/commands/backend/build.ts deleted file mode 100644 index c85eecfed3..0000000000 --- a/packages/cli/src/commands/backend/build.ts +++ /dev/null @@ -1,26 +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 { OptionValues } from 'commander'; -import { buildPackage, Output } from '../../lib/builder'; - -export default async (opts: OptionValues) => { - await buildPackage({ - outputs: new Set([Output.cjs, Output.types]), - minify: opts.minify, - useApiExtractor: opts.experimentalTypeBuild, - }); -}; diff --git a/packages/cli/src/commands/backend/bundle.ts b/packages/cli/src/commands/backend/bundle.ts deleted file mode 100644 index 78d4d72d92..0000000000 --- a/packages/cli/src/commands/backend/bundle.ts +++ /dev/null @@ -1,72 +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 os from 'os'; -import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; -import tar, { CreateOptions } from 'tar'; -import { OptionValues } from 'commander'; -import { createDistWorkspace } from '../../lib/packager'; -import { paths } from '../../lib/paths'; -import { getEnvironmentParallelism } from '../../lib/parallel'; -import { buildPackage, Output } from '../../lib/builder'; - -const BUNDLE_FILE = 'bundle.tar.gz'; -const SKELETON_FILE = 'skeleton.tar.gz'; - -export default async (opts: OptionValues) => { - const targetDir = paths.resolveTarget('dist'); - const pkg = await fs.readJson(paths.resolveTarget('package.json')); - - // We build the target package without generating type declarations. - await buildPackage({ outputs: new Set([Output.cjs]) }); - - const tmpDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-bundle')); - try { - await createDistWorkspace([pkg.name], { - targetDir: tmpDir, - buildDependencies: Boolean(opts.buildDependencies), - buildExcludes: [pkg.name], - parallelism: getEnvironmentParallelism(), - skeleton: SKELETON_FILE, - }); - - // We built the target backend package using the regular build process, but the result of - // that has now been packed into the dist workspace, so clean up the dist dir. - await fs.remove(targetDir); - await fs.mkdir(targetDir); - - // Move out skeleton.tar.gz before we create the main bundle, no point having that included up twice. - await fs.move( - resolvePath(tmpDir, SKELETON_FILE), - resolvePath(targetDir, SKELETON_FILE), - ); - - // Create main bundle.tar.gz, with some tweaks to make it more likely hit Docker build cache. - await tar.create( - { - file: resolvePath(targetDir, BUNDLE_FILE), - cwd: tmpDir, - portable: true, - noMtime: true, - gzip: true, - } as CreateOptions & { noMtime: boolean }, - [''], - ); - } finally { - await fs.remove(tmpDir); - } -}; diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts deleted file mode 100644 index 2ee43958a2..0000000000 --- a/packages/cli/src/commands/backend/dev.ts +++ /dev/null @@ -1,36 +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 fs from 'fs-extra'; -import { OptionValues } from 'commander'; -import { paths } from '../../lib/paths'; -import { serveBackend } from '../../lib/bundler/backend'; - -export default async (opts: OptionValues) => { - // Cleaning dist/ before we start the dev process helps work around an issue - // where we end up with the entrypoint executing multiple times, causing - // a port bind conflict among other things. - await fs.remove(paths.resolveTarget('dist')); - - const waitForExit = await serveBackend({ - entry: 'src/index', - checksEnabled: opts.check, - inspectEnabled: opts.inspect, - inspectBrkEnabled: opts.inspectBrk, - }); - - await waitForExit(); -}; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 1a7b2c4f08..aca238a32b 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -183,53 +183,6 @@ export function registerMigrateCommand(program: Command) { } export function registerCommands(program: Command) { - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('app:build') - .description('Build an app for a production release [DEPRECATED]') - .option('--stats', 'Write bundle stats to output directory') - .option(...configOption) - .action(lazy(() => import('./app/build').then(m => m.default))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('app:serve') - .description('Serve an app for local development [DEPRECATED]') - .option('--check', 'Enable type checking and linting') - .option(...configOption) - .action(lazy(() => import('./app/serve').then(m => m.default))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('backend:build') - .description('Build a backend plugin [DEPRECATED]') - .option('--minify', 'Minify the generated code') - .option('--experimental-type-build', 'Enable experimental type build') - .action(lazy(() => import('./backend/build').then(m => m.default))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('backend:bundle') - .description('Bundle the backend into a deployment archive [DEPRECATED]') - .option( - '--build-dependencies', - 'Build all local package dependencies before bundling the backend', - ) - .action(lazy(() => import('./backend/bundle').then(m => m.default))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('backend:dev') - .description( - 'Start local development server with HMR for the backend [DEPRECATED]', - ) - .option('--check', 'Enable type checking and linting') - .option('--inspect', 'Enable debugger') - .option('--inspect-brk', 'Enable debugger with await to attach debugger') - // We don't actually use the config in the CLI, just pass them on to the NodeJS process - .option(...configOption) - .action(lazy(() => import('./backend/dev').then(m => m.default))); - program .command('create') .storeOptionsAsProperties(false) @@ -268,22 +221,6 @@ export function registerCommands(program: Command) { lazy(() => import('./create-plugin/createPlugin').then(m => m.default)), ); - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('plugin:build') - .description('Build a plugin [DEPRECATED]') - .option('--minify', 'Minify the generated code') - .option('--experimental-type-build', 'Enable experimental type build') - .action(lazy(() => import('./plugin/build').then(m => m.default))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('plugin:serve') - .description('Serves the dev/ folder of a plugin [DEPRECATED]') - .option('--check', 'Enable type checking and linting') - .option(...configOption) - .action(lazy(() => import('./plugin/serve').then(m => m.default))); - program .command('plugin:diff') .option('--check', 'Fail if changes are required') @@ -291,27 +228,6 @@ export function registerCommands(program: Command) { .description('Diff an existing plugin with the creation template') .action(lazy(() => import('./plugin/diff').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('build') - .description('Build a package for publishing [DEPRECATED]') - .option('--outputs ', 'List of formats to output [types,cjs,esm]') - .option('--minify', 'Minify the generated code') - .option('--experimental-type-build', 'Enable experimental type build') - .action(lazy(() => import('./oldBuild').then(m => m.default))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('lint [directories...]') - .option( - '--format ', - 'Lint report output format', - 'eslint-formatter-friendly', - ) - .option('--fix', 'Attempt to automatically fix violations') - .description('Lint a package [DEPRECATED]') - .action(lazy(() => import('./lint').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of package variant program .command('test') @@ -400,22 +316,6 @@ export function registerCommands(program: Command) { .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('prepack') - .description( - 'Prepares a package for packaging before publishing [DEPRECATED]', - ) - .action(lazy(() => import('./pack').then(m => m.pre))); - - // TODO(Rugvip): Deprecate in favor of package variant - program - .command('postpack') - .description( - 'Restores the changes made by the prepack command [DEPRECATED]', - ) - .action(lazy(() => import('./pack').then(m => m.post))); - // TODO(Rugvip): Deprecate in favor of package variant program .command('clean') diff --git a/packages/cli/src/commands/oldBuild.ts b/packages/cli/src/commands/oldBuild.ts deleted file mode 100644 index c0899d4d02..0000000000 --- a/packages/cli/src/commands/oldBuild.ts +++ /dev/null @@ -1,41 +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 { buildPackage, Output } from '../lib/builder'; -import { OptionValues } from 'commander'; - -export default async (opts: OptionValues) => { - let outputs = new Set(); - - const { outputs: outputsStr } = opts as { outputs?: string }; - if (outputsStr) { - for (const output of outputsStr.split(',') as (keyof typeof Output)[]) { - if (output in Output) { - outputs.add(Output[output]); - } else { - throw new Error(`Unknown output format: ${output}`); - } - } - } else { - outputs = new Set([Output.types, Output.esm, Output.cjs]); - } - - await buildPackage({ - outputs, - minify: opts.minify, - useApiExtractor: opts.experimentalTypeBuild, - }); -}; diff --git a/packages/cli/src/commands/plugin/build.ts b/packages/cli/src/commands/plugin/build.ts deleted file mode 100644 index 671aece818..0000000000 --- a/packages/cli/src/commands/plugin/build.ts +++ /dev/null @@ -1,26 +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 { OptionValues } from 'commander'; -import { buildPackage, Output } from '../../lib/builder'; - -export default async (opts: OptionValues) => { - await buildPackage({ - outputs: new Set([Output.esm, Output.types]), - minify: opts.minify, - useApiExtractor: opts.experimentalTypeBuild, - }); -}; diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts deleted file mode 100644 index 891db190e0..0000000000 --- a/packages/cli/src/commands/plugin/serve.ts +++ /dev/null @@ -1,36 +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 fs from 'fs-extra'; -import { OptionValues } from 'commander'; -import { serveBundle } from '../../lib/bundler'; -import { loadCliConfig } from '../../lib/config'; -import { paths } from '../../lib/paths'; - -export default async (opts: OptionValues) => { - const { name } = await fs.readJson(paths.resolveTarget('package.json')); - const waitForExit = await serveBundle({ - entry: 'dev/index', - checksEnabled: opts.check, - ...(await loadCliConfig({ - args: opts.config, - fromPackage: name, - withFilteredKeys: true, - })), - }); - - await waitForExit(); -}; From 4dec75734442c696f3721ff1f8e023a618a2c1df Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Thu, 23 Jun 2022 09:09:52 -0400 Subject: [PATCH 73/73] Replace Harbor icon with link to GitHub content Signed-off-by: Taras Mankovski --- microsite/data/plugins/harbor.yaml | 2 +- microsite/static/img/harbor-logo.png | Bin 18741 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 microsite/static/img/harbor-logo.png diff --git a/microsite/data/plugins/harbor.yaml b/microsite/data/plugins/harbor.yaml index 64b2033021..f959613d4c 100644 --- a/microsite/data/plugins/harbor.yaml +++ b/microsite/data/plugins/harbor.yaml @@ -5,7 +5,7 @@ authorUrl: https://bestsellerit.com category: Discovery description: This plugin will show you information about Docker images within the Harbor cloud native registry. documentation: https://github.com/BESTSELLER/backstage-plugin-harbor/blob/master/README.md -iconUrl: img/harbor-logo.png +iconUrl: https://raw.githubusercontent.com/cncf/artwork/master/projects/harbor/icon/color/harbor-icon-color.svg npmPackageName: '@bestsellerit/backstage-plugin-harbor' tags: - goharbor diff --git a/microsite/static/img/harbor-logo.png b/microsite/static/img/harbor-logo.png deleted file mode 100644 index 5f852066f8a49c9f4ca43750f6b2bc91d81c8ca1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18741 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