From 401c10eba0eab2b72e2863771fac0acac7d1f4b4 Mon Sep 17 00:00:00 2001 From: andrmaz <60042277+andrmaz@users.noreply.github.com> Date: Tue, 13 Sep 2022 22:28:17 +0200 Subject: [PATCH 001/160] feat(edge): attach groups hierarchy arrows to the right side of their parent boxes Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- .../src/components/DependencyGraph/Edge.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index cc6a044beb..72a7547cda 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -25,7 +25,7 @@ import { DependencyEdge, LabelPosition, } from './types'; -import { ARROW_MARKER_ID, EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; +import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; import { DefaultLabel } from './DefaultLabel'; import dagre from 'dagre'; @@ -47,7 +47,7 @@ export type DependencyGraphEdgeClassKey = 'path' | 'label'; const useStyles = makeStyles( (theme: BackstageTheme) => ({ path: { - strokeWidth: 2, + strokeWidth: 1, stroke: theme.palette.textSubtle, fill: 'none', transition: `${theme.transitions.duration.shortest}ms`, @@ -80,7 +80,7 @@ const createPath = d3Shape .line() .x(d => d.x) .y(d => d.y) - .curve(d3Shape.curveMonotoneX); + .curve(d3Shape.curveStepBefore); export function Edge({ render = renderDefault, @@ -124,12 +124,7 @@ export function Edge({ return ( <> {path && ( - + )} {labelProps.label ? ( Date: Tue, 13 Sep 2022 22:43:44 +0200 Subject: [PATCH 002/160] feat(changelog): add changeset describing the change Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- .changeset/pink-pumas-double.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pink-pumas-double.md diff --git a/.changeset/pink-pumas-double.md b/.changeset/pink-pumas-double.md new file mode 100644 index 0000000000..ca4d40967f --- /dev/null +++ b/.changeset/pink-pumas-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Attach groups hierarchy arrows to the right side of their parent boxes From fbf2d1256b9c3bb80666ddcfb1a45f9c9ea1f56d Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 22 Sep 2022 00:17:52 +0200 Subject: [PATCH 003/160] move github search to the backend Signed-off-by: Kiss Miklos --- plugins/catalog-backend/package.json | 2 + .../src/ingestion/GitHubLocationAnalyzer.ts | 88 +++++++++++++++++++ .../src/ingestion/LocationAnalyzer.ts | 41 ++++++++- .../catalog-backend/src/ingestion/types.ts | 16 +++- .../src/service/CatalogBuilder.ts | 5 +- .../src/service/standaloneServer.ts | 1 + plugins/catalog-import/package.json | 1 + .../src/api/CatalogImportClient.ts | 86 ++---------------- yarn.lock | 5 +- 9 files changed, 159 insertions(+), 86 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index f5ab216a7f..1e2d732f35 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -38,6 +38,7 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", @@ -47,6 +48,7 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", + "@octokit/rest": "^19.0.4", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts new file mode 100644 index 0000000000..b96b50649e --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts @@ -0,0 +1,88 @@ +/* + * 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 { CatalogClient } from '@backstage/catalog-client'; +import { GitHubIntegration } from '@backstage/integration'; +import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { Octokit } from '@octokit/rest'; +import { trimEnd } from 'lodash'; +import { AnalyzeLocationExistingEntity, BaseLocationAnalyzer } from './types'; + +export type GitHubLocationAnalyzerOptions = { + integration: GitHubIntegration; + catalogFilename?: string; + + discovery: DiscoveryApi; +}; +export class GitHubLocationAnalyzer implements BaseLocationAnalyzer { + private readonly integration: GitHubIntegration; + private readonly catalogFilename: string; + private readonly discovery: DiscoveryApi; + + constructor(options: GitHubLocationAnalyzerOptions) { + this.integration = options.integration; + this.catalogFilename = options.catalogFilename || 'catalog-info.yaml'; + this.discovery = options.discovery; + } + + async analyze( + owner: string, + repo: string, + url: string, + ): Promise { + const octo = new Octokit({ + auth: this.integration.config.token, + baseUrl: this.integration.config.apiBaseUrl, + }); + const query = `filename:${this.catalogFilename} repo:${owner}/${repo} `; + + const catalogClient = new CatalogClient({ discoveryApi: this.discovery }); + + const searchResult = await octo.search.code({ q: query }).catch(e => { + throw new Error(`Couldn't search repository for metadata file, ${e}`); + }); + + const exists = searchResult.data.total_count > 0; + if (exists) { + const repoInformation = await octo.repos.get({ owner, repo }).catch(e => { + throw new Error(`Couldn't fetch repo data, ${e}`); + }); + const defaultBranch = repoInformation.data.default_branch; + + return await Promise.all( + searchResult.data.items + .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) + .map(async target => { + const result = await catalogClient.addLocation({ + type: 'url', + target, + dryRun: true, + }); + return { + target, + exists: result.exists, + entities: result.entities.map(e => ({ + kind: e.kind, + namespace: e.metadata.namespace ?? 'default', + name: e.metadata.name, + })), + }; + }), + ); + } + return []; + } +} diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index b84dff7de8..c7354a40bc 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -17,25 +17,40 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; -import { ScmIntegrationRegistry } from '@backstage/integration'; +import { + GitHubIntegration, + ScmIntegrationRegistry, +} from '@backstage/integration'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, } from './types'; +import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; + private readonly discovery: DiscoveryApi; - constructor(logger: Logger, scmIntegrations: ScmIntegrationRegistry) { + constructor( + logger: Logger, + scmIntegrations: ScmIntegrationRegistry, + discovery: DiscoveryApi, + ) { this.logger = logger; this.scmIntegrations = scmIntegrations; + this.discovery = discovery; } async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { + const integration = this.scmIntegrations.byUrl( + request.location.target, + ) as GitHubIntegration; const { owner, name } = parseGitUrl(request.location.target); + const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -45,8 +60,8 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { spec: { type: 'other', lifecycle: 'unknown' }, }; - const integration = this.scmIntegrations.byUrl(request.location.target); let annotationPrefix; + let analyzer; switch (integration?.type) { case 'azure': annotationPrefix = 'dev.azure.com'; @@ -56,6 +71,10 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; case 'github': annotationPrefix = 'github.com'; + analyzer = new GitHubLocationAnalyzer({ + integration, + discovery: this.discovery, + }); break; case 'gitlab': annotationPrefix = 'gitlab.com'; @@ -63,6 +82,22 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { default: break; } + if (analyzer) { + const existingEntityFiles = await analyzer.analyze( + owner, + name, + request.location.target, + ); + if (existingEntityFiles.length > 0) { + this.logger.debug( + `entity for ${request.location.target} already exists.`, + ); + return { + existingEntityFiles, + generateEntities: [], + }; + } + } if (annotationPrefix) { entity.metadata.annotations = { diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 2602bd080f..149795d166 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { RecursivePartial } from '../util/RecursivePartial'; import { LocationSpec } from '@backstage/plugin-catalog-node'; @@ -50,9 +50,9 @@ export type AnalyzeLocationResponse = { * @public */ export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; + target: string; + exists: boolean | undefined; + entities: CompoundEntityRef[]; }; /** @@ -98,3 +98,11 @@ export type AnalyzeLocationEntityField = { */ description: string; }; + +export interface BaseLocationAnalyzer { + analyze( + owner: string, + repo: string, + url: string, + ): Promise; +} diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index d47ace1454..2e20b0d4c3 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -96,6 +96,7 @@ import { RESOURCE_TYPE_CATALOG_ENTITY, } from '@backstage/plugin-catalog-common'; import { AuthorizedLocationService } from './AuthorizedLocationService'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; /** @public */ export type CatalogEnvironment = { @@ -104,6 +105,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; + discovery: DiscoveryApi; }; /** @@ -478,7 +480,8 @@ export class CatalogBuilder { ); const locationAnalyzer = - this.locationAnalyzer ?? new RepoLocationAnalyzer(logger, integrations); + this.locationAnalyzer ?? + new RepoLocationAnalyzer(logger, integrations, this.env.discovery); const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index e48515fb8b..51195ccdd7 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -70,6 +70,7 @@ export async function startStandaloneServer( config, reader, permissions, + discovery, }); const catalog = await builder.build(); diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2a4ea4642c..055955c9c7 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,6 +40,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/integration-react": "workspace:^", + "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index c12e391ec9..0533b558c8 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,7 +15,6 @@ */ import { CatalogApi } from '@backstage/catalog-client'; -import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigApi, DiscoveryApi, @@ -28,11 +27,10 @@ import { import { ScmAuthApi } from '@backstage/integration-react'; import { Octokit } from '@octokit/rest'; import { Base64 } from 'js-base64'; -import { PartialEntity } from '../types'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; -import { trimEnd } from 'lodash'; import { getBranchName, getCatalogFilename } from '../components/helpers'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; /** * The default implementation of the {@link CatalogImportApi}. @@ -105,16 +103,14 @@ export class CatalogImportClient implements CatalogImportApi { ); } - // TODO: this could be part of the analyze-location endpoint - const locations = await this.checkGitHubForExistingCatalogInfo({ - ...ghConfig, - url, + const analyzation = await this.analyzeLocation({ + repo: url, }); - if (locations.length > 0) { + if (analyzation.existingEntityFiles.length > 0) { return { type: 'locations', - locations, + locations: analyzation.existingEntityFiles, }; } @@ -122,9 +118,7 @@ export class CatalogImportClient implements CatalogImportApi { type: 'repository', integrationType: 'github', url: url, - generatedEntities: await this.generateEntityDefinitions({ - repo: url, - }), + generatedEntities: analyzation.generateEntities.map((x: any) => x.entity), }; } @@ -174,9 +168,9 @@ the component will become available.\n\nFor more information, read an \ } // TODO: this could be part of the catalog api - private async generateEntityDefinitions(options: { + private async analyzeLocation(options: { repo: string; - }): Promise { + }): Promise { const { token } = await this.identityApi.getCredentials(); const response = await fetch( `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, @@ -200,69 +194,7 @@ the component will become available.\n\nFor more information, read an \ } const payload = await response.json(); - return payload.generateEntities.map((x: any) => x.entity); - } - - // TODO: this response should better be part of the analyze-locations response and scm-independent / implemented per scm - private async checkGitHubForExistingCatalogInfo(options: { - url: string; - owner: string; - repo: string; - githubIntegrationConfig: GitHubIntegrationConfig; - }): Promise< - Array<{ - target: string; - entities: CompoundEntityRef[]; - }> - > { - const { url, owner, repo, githubIntegrationConfig } = options; - - const { token } = await this.scmAuthApi.getCredentials({ url }); - const octo = new Octokit({ - auth: token, - baseUrl: githubIntegrationConfig.apiBaseUrl, - }); - const catalogFilename = getCatalogFilename(this.configApi); - const query = `repo:${owner}/${repo}+filename:${catalogFilename}`; - - const searchResult = await octo.search.code({ q: query }).catch(e => { - throw new Error( - formatHttpErrorMessage( - "Couldn't search repository for metadata file.", - e, - ), - ); - }); - const exists = searchResult.data.total_count > 0; - if (exists) { - const repoInformation = await octo.repos.get({ owner, repo }).catch(e => { - throw new Error(formatHttpErrorMessage("Couldn't fetch repo data", e)); - }); - const defaultBranch = repoInformation.data.default_branch; - - return await Promise.all( - searchResult.data.items - .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) - .map(async target => { - const result = await this.catalogApi.addLocation({ - type: 'url', - target, - dryRun: true, - }); - return { - target, - exists: result.exists, - entities: result.entities.map(e => ({ - kind: e.kind, - namespace: e.metadata.namespace ?? 'default', - name: e.metadata.name, - })), - }; - }), - ); - } - - return []; + return payload; } // TODO: extract this function and implement for non-github diff --git a/yarn.lock b/yarn.lock index 1178b3d940..fbb8955c8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4659,6 +4659,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" @@ -4669,6 +4670,7 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/types": "workspace:^" + "@octokit/rest": ^19.0.4 "@types/core-js": ^2.5.4 "@types/express": ^4.17.6 "@types/git-url-parse": ^9.0.0 @@ -4796,6 +4798,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 @@ -11576,7 +11579,7 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^19.0.3": +"@octokit/rest@npm:^19.0.3, @octokit/rest@npm:^19.0.4": version: 19.0.4 resolution: "@octokit/rest@npm:19.0.4" dependencies: From 272d95bd70993ce2db9f13c7a51ce0e0d8f10295 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 22 Sep 2022 00:20:25 +0200 Subject: [PATCH 004/160] more useful name Signed-off-by: Kiss Miklos --- .../catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts | 4 ++-- plugins/catalog-backend/src/ingestion/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts index b96b50649e..885544bfd5 100644 --- a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts @@ -19,7 +19,7 @@ import { GitHubIntegration } from '@backstage/integration'; import { DiscoveryApi } from '@backstage/plugin-permission-common'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; -import { AnalyzeLocationExistingEntity, BaseLocationAnalyzer } from './types'; +import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types'; export type GitHubLocationAnalyzerOptions = { integration: GitHubIntegration; @@ -27,7 +27,7 @@ export type GitHubLocationAnalyzerOptions = { discovery: DiscoveryApi; }; -export class GitHubLocationAnalyzer implements BaseLocationAnalyzer { +export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { private readonly integration: GitHubIntegration; private readonly catalogFilename: string; private readonly discovery: DiscoveryApi; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 149795d166..f7b1760441 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -99,7 +99,7 @@ export type AnalyzeLocationEntityField = { description: string; }; -export interface BaseLocationAnalyzer { +export interface ScmLocationAnalyzer { analyze( owner: string, repo: string, From af7fffa3a40c3922a26a3eee4405a48ae943d902 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 22 Sep 2022 01:07:23 +0200 Subject: [PATCH 005/160] api-report Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 9 ++++++--- plugins/catalog-backend/src/service/CatalogPlugin.ts | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f18d396114..d3abca1c94 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -18,10 +18,12 @@ import { CatalogProcessorParser } from '@backstage/plugin-catalog-node'; import { CatalogProcessorRefreshKeysResult } from '@backstage/plugin-catalog-node'; import { CatalogProcessorRelationResult } from '@backstage/plugin-catalog-node'; import { CatalogProcessorResult } from '@backstage/plugin-catalog-node'; +import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; import { DeferredEntity } from '@backstage/plugin-catalog-node'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; @@ -64,9 +66,9 @@ export type AnalyzeLocationEntityField = { // @public export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; + target: string; + exists: boolean | undefined; + entities: CompoundEntityRef[]; }; // @public @@ -218,6 +220,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; + discovery: DiscoveryApi; }; // @alpha diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 64e1e52494..3619460c4d 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -22,6 +22,7 @@ import { permissionsServiceRef, urlReaderServiceRef, httpRouterServiceRef, + discoveryServiceRef, } from '@backstage/backend-plugin-api'; import { CatalogBuilder } from './CatalogBuilder'; import { @@ -78,6 +79,7 @@ export const catalogPlugin = createBackendPlugin({ permissions: permissionsServiceRef, database: databaseServiceRef, httpRouter: httpRouterServiceRef, + discovery: discoveryServiceRef, }, async init({ logger, @@ -86,6 +88,7 @@ export const catalogPlugin = createBackendPlugin({ database, permissions, httpRouter, + discovery, }) { const winstonLogger = loggerToWinstonLogger(logger); const builder = await CatalogBuilder.create({ @@ -94,6 +97,7 @@ export const catalogPlugin = createBackendPlugin({ permissions, database, logger: winstonLogger, + discovery, }); builder.addProcessor(...processingExtensions.processors); builder.addEntityProvider(...processingExtensions.entityProviders); From b2e6cb6acfb4e5c452ee89704b61ae0d63c4eb2c Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Sat, 24 Sep 2022 17:59:57 +0200 Subject: [PATCH 006/160] add changeset Signed-off-by: Kiss Miklos --- .changeset/curvy-pets-wash.md | 5 +++++ .changeset/fuzzy-dolls-shake.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/curvy-pets-wash.md create mode 100644 .changeset/fuzzy-dolls-shake.md diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md new file mode 100644 index 0000000000..7e43228fbc --- /dev/null +++ b/.changeset/curvy-pets-wash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md new file mode 100644 index 0000000000..92380b37ec --- /dev/null +++ b/.changeset/fuzzy-dolls-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials From c0833d0d3f319602b975ca5d08ff14180b27c8dc Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 26 Sep 2022 12:19:15 +0200 Subject: [PATCH 007/160] use the correct type Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 3 +-- plugins/catalog-backend/package.json | 1 - plugins/catalog-backend/src/service/CatalogBuilder.ts | 9 ++++++--- yarn.lock | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d3abca1c94..e1aec05e33 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -23,7 +23,6 @@ import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; import { DeferredEntity } from '@backstage/plugin-catalog-node'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; @@ -220,7 +219,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: DiscoveryApi; + discovery: PluginEndpointDiscovery; }; // @alpha diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 1e2d732f35..8232f1d05a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -38,7 +38,6 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 2e20b0d4c3..f1d29f015c 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; +import { + PluginDatabaseManager, + PluginEndpointDiscovery, + UrlReader, +} from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, Entity, @@ -96,7 +100,6 @@ import { RESOURCE_TYPE_CATALOG_ENTITY, } from '@backstage/plugin-catalog-common'; import { AuthorizedLocationService } from './AuthorizedLocationService'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; /** @public */ export type CatalogEnvironment = { @@ -105,7 +108,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: DiscoveryApi; + discovery: PluginEndpointDiscovery; }; /** diff --git a/yarn.lock b/yarn.lock index fbb8955c8f..c3155dbfee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4659,7 +4659,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" From a08b7939bb4f6fb436ea2e6e09b15da3ec340bce Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 26 Sep 2022 12:53:47 +0200 Subject: [PATCH 008/160] use the correct discovery type Signed-off-by: Kiss Miklos --- plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index c7354a40bc..1fbb6a3d47 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -26,18 +26,18 @@ import { AnalyzeLocationResponse, LocationAnalyzer, } from './types'; -import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; - private readonly discovery: DiscoveryApi; + private readonly discovery: PluginEndpointDiscovery; constructor( logger: Logger, scmIntegrations: ScmIntegrationRegistry, - discovery: DiscoveryApi, + discovery: PluginEndpointDiscovery, ) { this.logger = logger; this.scmIntegrations = scmIntegrations; @@ -82,6 +82,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { default: break; } + if (analyzer) { const existingEntityFiles = await analyzer.analyze( owner, From 73db439b1d799717e4f3e70e6cbeb9e68f1227ee Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 26 Sep 2022 14:08:29 +0200 Subject: [PATCH 009/160] do not change public facing api Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 7 ++--- .../src/ingestion/GitHubLocationAnalyzer.ts | 21 ++++++-------- .../catalog-backend/src/ingestion/types.ts | 8 ++--- .../src/api/CatalogImportClient.ts | 29 ++++++++++++++++++- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index e1aec05e33..5745588873 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -18,7 +18,6 @@ import { CatalogProcessorParser } from '@backstage/plugin-catalog-node'; import { CatalogProcessorRefreshKeysResult } from '@backstage/plugin-catalog-node'; import { CatalogProcessorRelationResult } from '@backstage/plugin-catalog-node'; import { CatalogProcessorResult } from '@backstage/plugin-catalog-node'; -import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; @@ -65,9 +64,9 @@ export type AnalyzeLocationEntityField = { // @public export type AnalyzeLocationExistingEntity = { - target: string; - exists: boolean | undefined; - entities: CompoundEntityRef[]; + location: LocationSpec; + isRegistered: boolean; + entity: Entity; }; // @public diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts index 885544bfd5..46fa87cd92 100644 --- a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts @@ -24,7 +24,6 @@ import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types'; export type GitHubLocationAnalyzerOptions = { integration: GitHubIntegration; catalogFilename?: string; - discovery: DiscoveryApi; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { @@ -62,26 +61,24 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { }); const defaultBranch = repoInformation.data.default_branch; - return await Promise.all( + const result = await Promise.all( searchResult.data.items .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) .map(async target => { - const result = await catalogClient.addLocation({ + const addLocationResult = await catalogClient.addLocation({ type: 'url', target, dryRun: true, }); - return { - target, - exists: result.exists, - entities: result.entities.map(e => ({ - kind: e.kind, - namespace: e.metadata.namespace ?? 'default', - name: e.metadata.name, - })), - }; + return addLocationResult.entities.map(e => ({ + location: { type: 'url', target }, + isRegistered: !!addLocationResult.exists, + entity: e, + })); }), ); + + return result.flat(); } return []; } diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index f7b1760441..9da7b037aa 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { RecursivePartial } from '../util/RecursivePartial'; import { LocationSpec } from '@backstage/plugin-catalog-node'; @@ -50,9 +50,9 @@ export type AnalyzeLocationResponse = { * @public */ export type AnalyzeLocationExistingEntity = { - target: string; - exists: boolean | undefined; - entities: CompoundEntityRef[]; + location: LocationSpec; + isRegistered: boolean; + entity: Entity; }; /** diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 0533b558c8..7aa3365488 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -31,6 +31,7 @@ import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; import { getBranchName, getCatalogFilename } from '../components/helpers'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; +import { CompoundEntityRef } from '@backstage/catalog-model'; /** * The default implementation of the {@link CatalogImportApi}. @@ -108,9 +109,35 @@ export class CatalogImportClient implements CatalogImportApi { }); if (analyzation.existingEntityFiles.length > 0) { + const locations = analyzation.existingEntityFiles.reduce< + Record< + string, + { + target: string; + exists?: boolean; + entities: CompoundEntityRef[]; + } + > + >((state, curr) => { + state[curr.location.target] = { + target: curr.location.target, + exists: curr.isRegistered, + entities: [ + ...(curr.location.target in state + ? state[curr.location.target].entities + : []), + { + name: curr.entity.metadata.name, + namespace: curr.entity.metadata.namespace ?? 'default', + kind: curr.entity.kind, + }, + ], + }; + return state; + }, {}); return { type: 'locations', - locations: analyzation.existingEntityFiles, + locations: Object.values(locations), }; } From 421b620af34f9afbbc028cd6fee2bdde5bd29c76 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 10:46:15 +0200 Subject: [PATCH 010/160] add tests Signed-off-by: Kiss Miklos --- .../src/ingestion/LocationAnalyzer.ts | 4 +- .../analyzers/GitHubLocationAnalyzer.test.ts | 126 ++++++++++++++++++ .../{ => analyzers}/GitHubLocationAnalyzer.ts | 47 +++---- 3 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts rename plugins/catalog-backend/src/ingestion/{ => analyzers}/GitHubLocationAnalyzer.ts (67%) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 1fbb6a3d47..efd6a05fcf 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -27,7 +27,7 @@ import { LocationAnalyzer, } from './types'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; +import { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; @@ -85,8 +85,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { if (analyzer) { const existingEntityFiles = await analyzer.analyze( - owner, - name, request.location.target, ); if (existingEntityFiles.length > 0) { diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts new file mode 100644 index 0000000000..11bb3137c7 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts @@ -0,0 +1,126 @@ +/* + * 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. + */ + +const octokit = { + search: { + code: jest.fn(), + }, + repos: { + get: jest.fn(), + }, +}; + +jest.mock('@octokit/rest', () => { + class Octokit { + constructor() { + return octokit; + } + } + return { Octokit }; +}); + +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { GitHubIntegration } from '@backstage/integration'; + +const server = setupServer(); + +describe('GitHubLocationAnalyzer', () => { + const mockDiscoveryApi: jest.Mocked = { + getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), + getExternalBaseUrl: jest.fn(), + }; + const integration = new GitHubIntegration({ + host: 'h.com', + apiBaseUrl: 'a', + rawBaseUrl: 'r', + token: 't', + }); + + setupRequestMockHandlers(server); + + beforeEach(() => { + server.use( + rest.post('http://localhost:7007/locations', async (req, res, ctx) => { + return res( + ctx.status(201), + ctx.json({ + location: 'test', + exists: false, + entities: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'test-entity', + }, + spec: { + type: 'url', + target: 'whatever', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + title: 'Test Entity', + name: 'test-entity-2', + description: 'The expected description 2', + }, + spec: { + type: 'some-type', + lifecycle: 'experimental', + owner: 'someone', + }, + }, + ], + }), + ); + }), + ); + }); + + it('should analyze', async () => { + octokit.search.code.mockImplementation((opts: { q: string }) => { + if (opts.q === 'filename:catalog-info.yaml repo:foo/bar') { + return Promise.resolve({ + data: { items: [{ path: 'catalog-info.yaml' }], total_count: 1 }, + }); + } + return Promise.reject(); + }); + + octokit.repos.get.mockResolvedValue({ + data: { default_branch: 'my_default_branch' }, + }); + + const analyzer = new GitHubLocationAnalyzer({ + discovery: mockDiscoveryApi, + integration, + }); + const result = await analyzer.analyze('https://github.com/foo/bar'); + + expect(result[0].isRegistered).toBeFalsy(); + expect(result[0].location).toEqual({ + type: 'url', + target: + 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', + }); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts similarity index 67% rename from plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts rename to plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts index 46fa87cd92..2e42f202f9 100644 --- a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { CatalogClient } from '@backstage/catalog-client'; +import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { GitHubIntegration } from '@backstage/integration'; import { DiscoveryApi } from '@backstage/plugin-permission-common'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; -import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types'; +import parseGitUrl from 'git-url-parse'; +import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from '../types'; export type GitHubLocationAnalyzerOptions = { integration: GitHubIntegration; @@ -27,45 +28,45 @@ export type GitHubLocationAnalyzerOptions = { discovery: DiscoveryApi; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { - private readonly integration: GitHubIntegration; private readonly catalogFilename: string; private readonly discovery: DiscoveryApi; + private readonly octokitClient: Octokit; + private readonly catalogClient: CatalogApi; constructor(options: GitHubLocationAnalyzerOptions) { - this.integration = options.integration; this.catalogFilename = options.catalogFilename || 'catalog-info.yaml'; this.discovery = options.discovery; + this.octokitClient = new Octokit({ + auth: options.integration.config.token, + baseUrl: options.integration.config.apiBaseUrl, + }); + this.catalogClient = new CatalogClient({ discoveryApi: this.discovery }); } - async analyze( - owner: string, - repo: string, - url: string, - ): Promise { - const octo = new Octokit({ - auth: this.integration.config.token, - baseUrl: this.integration.config.apiBaseUrl, - }); - const query = `filename:${this.catalogFilename} repo:${owner}/${repo} `; + async analyze(url: string): Promise { + const { owner, name: repo } = parseGitUrl(url); + const query = `filename:${this.catalogFilename} repo:${owner}/${repo}`; - const catalogClient = new CatalogClient({ discoveryApi: this.discovery }); - - const searchResult = await octo.search.code({ q: query }).catch(e => { - throw new Error(`Couldn't search repository for metadata file, ${e}`); - }); + const searchResult = await this.octokitClient.search + .code({ q: query }) + .catch(e => { + throw new Error(`Couldn't search repository for metadata file, ${e}`); + }); const exists = searchResult.data.total_count > 0; if (exists) { - const repoInformation = await octo.repos.get({ owner, repo }).catch(e => { - throw new Error(`Couldn't fetch repo data, ${e}`); - }); + const repoInformation = await this.octokitClient.repos + .get({ owner, repo }) + .catch(e => { + throw new Error(`Couldn't fetch repo data, ${e}`); + }); const defaultBranch = repoInformation.data.default_branch; const result = await Promise.all( searchResult.data.items .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) .map(async target => { - const addLocationResult = await catalogClient.addLocation({ + const addLocationResult = await this.catalogClient.addLocation({ type: 'url', target, dryRun: true, From 48117acb8cc65825931b2473f5d1b3f1915c16b3 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 15:49:01 +0200 Subject: [PATCH 011/160] refactor Signed-off-by: Kiss Miklos --- .../src/ingestion/LocationAnalyzer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index efd6a05fcf..f1fede65df 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -51,15 +51,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { ) as GitHubIntegration; const { owner, name } = parseGitUrl(request.location.target); - const entity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: name, - }, - spec: { type: 'other', lifecycle: 'unknown' }, - }; - let annotationPrefix; let analyzer; switch (integration?.type) { @@ -98,6 +89,15 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { } } + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: name, + }, + spec: { type: 'other', lifecycle: 'unknown' }, + }; + if (annotationPrefix) { entity.metadata.annotations = { [`${annotationPrefix}/project-slug`]: `${owner}/${name}`, From fe99c5d41466d814f43b805093265205f12f85d1 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 15:56:04 +0200 Subject: [PATCH 012/160] fox tsc Signed-off-by: Kiss Miklos --- .../src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts index 11bb3137c7..7f02fee620 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts @@ -57,7 +57,7 @@ describe('GitHubLocationAnalyzer', () => { beforeEach(() => { server.use( - rest.post('http://localhost:7007/locations', async (req, res, ctx) => { + rest.post('http://localhost:7007/locations', async (_, res, ctx) => { return res( ctx.status(201), ctx.json({ @@ -123,4 +123,5 @@ describe('GitHubLocationAnalyzer', () => { 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', }); }); + it('should'); }); From 11f8f63b0705d0c079938961ceba6442f4146441 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 19:21:21 +0200 Subject: [PATCH 013/160] test optional entity filename for search Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 1 + .../src/ingestion/LocationAnalyzer.ts | 1 + .../analyzers/GitHubLocationAnalyzer.test.ts | 31 +++- .../catalog-backend/src/ingestion/types.ts | 1 + .../src/service/createRouter.ts | 10 +- .../src/api/CatalogImportClient.test.ts | 162 +++++++++++++----- .../src/api/CatalogImportClient.ts | 7 + 7 files changed, 164 insertions(+), 49 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 5745588873..bf53badd87 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -78,6 +78,7 @@ export type AnalyzeLocationGenerateEntity = { // @public (undocumented) export type AnalyzeLocationRequest = { location: LocationSpec; + catalogFilename?: string; }; // @public (undocumented) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index f1fede65df..24c5821d92 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -65,6 +65,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { analyzer = new GitHubLocationAnalyzer({ integration, discovery: this.discovery, + catalogFilename: request.catalogFilename, }); break; case 'gitlab': diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts index 7f02fee620..fbc9ae3454 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts @@ -94,6 +94,10 @@ describe('GitHubLocationAnalyzer', () => { ); }), ); + + octokit.repos.get.mockResolvedValue({ + data: { default_branch: 'my_default_branch' }, + }); }); it('should analyze', async () => { @@ -106,10 +110,6 @@ describe('GitHubLocationAnalyzer', () => { return Promise.reject(); }); - octokit.repos.get.mockResolvedValue({ - data: { default_branch: 'my_default_branch' }, - }); - const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, integration, @@ -123,5 +123,26 @@ describe('GitHubLocationAnalyzer', () => { 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', }); }); - it('should'); + it('should use the provided entity filename for search', async () => { + octokit.search.code.mockImplementation((opts: { q: string }) => { + if (opts.q === 'filename:anvil.yaml repo:foo/bar') { + return Promise.resolve({ + data: { items: [{ path: 'anvil.yaml' }], total_count: 1 }, + }); + } + return Promise.reject(); + }); + + const analyzer = new GitHubLocationAnalyzer({ + discovery: mockDiscoveryApi, + integration, + catalogFilename: 'anvil.yaml', + }); + const result = await analyzer.analyze('https://github.com/foo/bar'); + + expect(result[0].location).toEqual({ + type: 'url', + target: 'https://github.com/foo/bar/blob/my_default_branch/anvil.yaml', + }); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 9da7b037aa..8dc833abd8 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -34,6 +34,7 @@ export type LocationAnalyzer = { /** @public */ export type AnalyzeLocationRequest = { location: LocationSpec; + catalogFilename?: string; }; /** @public */ diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 639893dcc1..93a4e20726 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -229,9 +229,15 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const body = await validateRequestBody( req, - z.object({ location: locationInput }), + z.object({ + location: locationInput, + catalogFilename: z.string().optional(), + }), ); - const schema = z.object({ location: locationInput }); + const schema = z.object({ + location: locationInput, + catalogFilename: z.string().optional(), + }); const output = await locationAnalyzer.analyzeLocation(schema.parse(body)); res.status(200).json(output); }); diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 30b9f47d99..d507ac3bef 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -297,7 +297,7 @@ describe('CatalogImportClient', () => { it('should find locations from github', async () => { (new Octokit().search.code as any as jest.Mock).mockResolvedValueOnce({ data: { - total_count: 2, + total_count: 3, items: [ { path: 'simple/path/catalog-info.yaml' }, { path: 'co/mple/x/path/catalog-info.yaml' }, @@ -305,24 +305,72 @@ describe('CatalogImportClient', () => { ], }, }); - - catalogApi.addLocation.mockImplementation(async ({ type, target }) => ({ - location: { - id: 'id-0', - type: type ?? 'url', - target, - }, - entities: [ - { - apiVersion: '1', - kind: 'k', - metadata: { - name: 'e', - namespace: 'n', + server.use( + rest.post(`${mockBaseUrl}/analyze-location`, (req, res, ctx) => { + expect(req.body).toEqual({ + location: { + target: 'https://github.com/backstage/backstage', + type: 'url', }, - }, - ], - })); + }); + + return res( + ctx.json({ + generateEntities: [], + existingEntityFiles: [ + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/main/simple/path/catalog-info.yaml', + }, + entity: { + apiVersion: '1', + kind: 'k', + metadata: { + name: 'e', + namespace: 'n', + }, + }, + }, + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/main/co/mple/x/path/catalog-info.yaml', + }, + entity: { + apiVersion: '1', + kind: 'k', + metadata: { + name: 'e', + namespace: 'n', + }, + }, + }, + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/main/catalog-info.yaml', + }, + entity: { + apiVersion: '1', + kind: 'k', + metadata: { + name: 'e', + namespace: 'n', + }, + }, + }, + ], + }), + ); + }), + ); await expect( catalogImportClient.analyzeUrl( @@ -332,16 +380,19 @@ describe('CatalogImportClient', () => { locations: [ { entities: [{ kind: 'k', name: 'e', namespace: 'n' }], + exists: false, target: 'https://github.com/backstage/backstage/blob/main/simple/path/catalog-info.yaml', }, { entities: [{ kind: 'k', name: 'e', namespace: 'n' }], + exists: false, target: 'https://github.com/backstage/backstage/blob/main/co/mple/x/path/catalog-info.yaml', }, { entities: [{ kind: 'k', name: 'e', namespace: 'n' }], + exists: false, target: 'https://github.com/backstage/backstage/blob/main/catalog-info.yaml', }, @@ -426,31 +477,57 @@ describe('CatalogImportClient', () => { }), ); - catalogApi.addLocation.mockImplementation(async ({ type, target }) => ({ - location: { - id: 'id-0', - type: type ?? 'url', - target, - }, - entities: [ - { - apiVersion: '1', - kind: 'Location', - metadata: { - name: 'my-entity', - namespace: 'my-namespace', + server.use( + rest.post(`${mockBaseUrl}/analyze-location`, (req, res, ctx) => { + expect(req.body).toEqual({ + location: { + target: 'https://github.com/acme-corp/our-awesome-api', + type: 'url', }, - }, - { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'my-entity', - namespace: 'my-namespace', - }, - }, - ], - })); + catalogFilename: 'anvil.yaml', + }); + + return res( + ctx.json({ + generateEntities: [], + existingEntityFiles: [ + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml', + }, + entity: { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + }, + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml', + }, + entity: { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + }, + ], + }), + ); + }), + ); await expect( catalogImportClient.analyzeUrl(repositoryUrl), @@ -470,6 +547,7 @@ describe('CatalogImportClient', () => { }, ], target: `${repositoryUrl}/blob/main/${entityFilename}`, + exists: false, }, ], type: 'locations', diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 7aa3365488..5dd3a3178f 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -209,6 +209,13 @@ the component will become available.\n\nFor more information, read an \ method: 'POST', body: JSON.stringify({ location: { type: 'url', target: options.repo }, + ...(this.configApi.getOptionalString( + 'catalog.import.entityFilename', + ) && { + catalogFilename: this.configApi.getOptionalString( + 'catalog.import.entityFilename', + ), + }), }), }, ).catch(e => { From 7cdf917b082b8a6cda2adc9ccceb5109462b5989 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:14:15 +0000 Subject: [PATCH 014/160] Update dependency @codemirror/view to v6.3.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 d1a6809ab6..c7be844a40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8107,13 +8107,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0": - version: 6.2.5 - resolution: "@codemirror/view@npm:6.2.5" + version: 6.3.0 + resolution: "@codemirror/view@npm:6.3.0" dependencies: "@codemirror/state": ^6.0.0 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: 0db9c3475790531912a86d5bb60f895ea11d19786a4c877703a602c840d45c2fd92ad6aea6b8648c7dccf88b7250ee1cbc5ca1c1797e5410440e7eb17a970a62 + checksum: 8fe39a1c47799b9bca8b815d7fd5c33bcde06cc8b98c26c87729ccca6d5afbc005671c8b00d8678209bcfc079f6894f4e61f26618d8b8133a79befa0828c789f languageName: node linkType: hard From 59ad2b9d259d04b96f001a8b4b5cb6e15e0ceef8 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:28:26 +0200 Subject: [PATCH 015/160] move github deps to their own plugin Signed-off-by: Kiss Miklos --- .../package.json | 3 + .../analyzers/GitHubLocationAnalyzer.test.ts | 29 ++++++---- .../src}/analyzers/GitHubLocationAnalyzer.ts | 58 ++++++++++++------- .../src/index.ts | 1 + .../src/lib/github.ts | 33 +++++++++++ .../src/lib/index.ts | 1 + .../src/ingestion/LocationAnalyzer.ts | 24 ++++---- .../catalog-backend/src/ingestion/index.ts | 2 + .../catalog-backend/src/ingestion/types.ts | 18 +++--- .../src/service/CatalogBuilder.ts | 23 +++++++- yarn.lock | 1 + 11 files changed, 140 insertions(+), 53 deletions(-) rename plugins/{catalog-backend/src/ingestion => catalog-backend-module-github/src}/analyzers/GitHubLocationAnalyzer.test.ts (89%) rename plugins/{catalog-backend/src/ingestion => catalog-backend-module-github/src}/analyzers/GitHubLocationAnalyzer.ts (62%) diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 23d9cbecfc..950c7723e8 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -36,6 +36,7 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -44,6 +45,8 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/graphql": "^5.0.0", + "@octokit/rest": "^19.0.4", + "git-url-parse": "^13.1.0", "lodash": "^4.17.21", "msw": "^0.47.0", "node-fetch": "^2.6.7", diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts similarity index 89% rename from plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts rename to plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index fbc9ae3454..5ceb9cf85c 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -46,12 +46,17 @@ describe('GitHubLocationAnalyzer', () => { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), getExternalBaseUrl: jest.fn(), }; - const integration = new GitHubIntegration({ - host: 'h.com', - apiBaseUrl: 'a', - rawBaseUrl: 'r', - token: 't', - }); + const integrations = { + list: jest.fn(), + byHost: jest.fn(), + byUrl: () => + new GitHubIntegration({ + host: 'h.com', + apiBaseUrl: 'a', + rawBaseUrl: 'r', + token: 't', + }), + }; setupRequestMockHandlers(server); @@ -112,9 +117,11 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integration, + integrations, + }); + const result = await analyzer.analyze({ + url: 'https://github.com/foo/bar', }); - const result = await analyzer.analyze('https://github.com/foo/bar'); expect(result[0].isRegistered).toBeFalsy(); expect(result[0].location).toEqual({ @@ -135,10 +142,12 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integration, + integrations, + }); + const result = await analyzer.analyze({ + url: 'https://github.com/foo/bar', catalogFilename: 'anvil.yaml', }); - const result = await analyzer.analyze('https://github.com/foo/bar'); expect(result[0].location).toEqual({ type: 'url', diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts similarity index 62% rename from plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts rename to plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 2e42f202f9..218d56410d 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -15,39 +15,57 @@ */ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { GitHubIntegration } from '@backstage/integration'; -import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { + GitHubIntegration, + ScmIntegrationsGroup, +} from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; -import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from '../types'; +import { + AnalyzeLocationExistingEntity, + AnalyzeOptions, + ScmLocationAnalyzer, +} from '@backstage/plugin-catalog-backend'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; export type GitHubLocationAnalyzerOptions = { - integration: GitHubIntegration; + integrations: ScmIntegrationsGroup; catalogFilename?: string; - discovery: DiscoveryApi; + discovery: PluginEndpointDiscovery; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { - private readonly catalogFilename: string; - private readonly discovery: DiscoveryApi; - private readonly octokitClient: Octokit; private readonly catalogClient: CatalogApi; + private readonly integrations: ScmIntegrationsGroup; constructor(options: GitHubLocationAnalyzerOptions) { - this.catalogFilename = options.catalogFilename || 'catalog-info.yaml'; - this.discovery = options.discovery; - this.octokitClient = new Octokit({ - auth: options.integration.config.token, - baseUrl: options.integration.config.apiBaseUrl, - }); - this.catalogClient = new CatalogClient({ discoveryApi: this.discovery }); + this.integrations = options.integrations; + this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); } - - async analyze(url: string): Promise { + getIntegrationType() { + return 'github'; + } + async analyze({ + url, + catalogFilename, + }: AnalyzeOptions): Promise { const { owner, name: repo } = parseGitUrl(url); - const query = `filename:${this.catalogFilename} repo:${owner}/${repo}`; - const searchResult = await this.octokitClient.search + const catalogFile = catalogFilename || 'catalog-info.yaml'; + + const query = `filename:${catalogFile} repo:${owner}/${repo}`; + + const integration = this.integrations.byUrl(url); + if (!integration) { + throw new Error('Make sure you have a GitHub integration configured'); + } + + const octokitClient = new Octokit({ + auth: integration.config.token, + baseUrl: integration.config.apiBaseUrl, + }); + + const searchResult = await octokitClient.search .code({ q: query }) .catch(e => { throw new Error(`Couldn't search repository for metadata file, ${e}`); @@ -55,7 +73,7 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { const exists = searchResult.data.total_count > 0; if (exists) { - const repoInformation = await this.octokitClient.repos + const repoInformation = await octokitClient.repos .get({ owner, repo }) .catch(e => { throw new Error(`Couldn't fetch repo data, ${e}`); diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index d793d48f1e..e856277873 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -29,3 +29,4 @@ export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntity export type { GithubMultiOrgConfig } from './lib'; export { githubEntityProviderCatalogModule } from './module'; export type { GithubEntityProviderCatalogModuleOptions } from './module'; +export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 6a944af330..ec904a52e0 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -308,6 +308,39 @@ export async function getOrganizationRepositories( return { repositories }; } +export async function getRepository( + client: typeof graphql, + org: string, + name: string, +): Promise { + const query = ` + query repositories($org: String!, $name: String!) { + repository(name: $name, owner: $org) { + name + url + isArchived + repositoryTopics(first: 100) { + nodes { + ... on RepositoryTopic { + topic { + name + } + } + } + } + defaultBranchRef { + name + } + } + }`; + + const repository: Repository = await client(query, { + org, + name, + }); + return repository; +} + /** * Gets all the users out of a GitHub organization. * diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 26ec3e8516..28cbe898d6 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -20,6 +20,7 @@ export { getOrganizationRepositories, getOrganizationTeams, getOrganizationUsers, + getRepository, } from './github'; export { assignGroupsToUsers, buildOrgHierarchy } from './org'; export { parseGitHubOrgUrl } from './util'; diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 24c5821d92..7992903b65 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -25,23 +25,22 @@ import { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, + ScmLocationAnalyzer, } from './types'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; - private readonly discovery: PluginEndpointDiscovery; + private readonly analyzers: ScmLocationAnalyzer[]; constructor( logger: Logger, scmIntegrations: ScmIntegrationRegistry, - discovery: PluginEndpointDiscovery, + analyzers: ScmLocationAnalyzer[], ) { this.logger = logger; this.scmIntegrations = scmIntegrations; - this.discovery = discovery; + this.analyzers = analyzers; } async analyzeLocation( request: AnalyzeLocationRequest, @@ -52,7 +51,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { const { owner, name } = parseGitUrl(request.location.target); let annotationPrefix; - let analyzer; switch (integration?.type) { case 'azure': annotationPrefix = 'dev.azure.com'; @@ -62,11 +60,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; case 'github': annotationPrefix = 'github.com'; - analyzer = new GitHubLocationAnalyzer({ - integration, - discovery: this.discovery, - catalogFilename: request.catalogFilename, - }); break; case 'gitlab': annotationPrefix = 'gitlab.com'; @@ -75,10 +68,13 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; } + const analyzer = this.analyzers.find( + a => a.getIntegrationType() === integration.type, + ); if (analyzer) { - const existingEntityFiles = await analyzer.analyze( - request.location.target, - ); + const existingEntityFiles = await analyzer.analyze({ + url: request.location.target, + }); if (existingEntityFiles.length > 0) { this.logger.debug( `entity for ${request.location.target} already exists.`, diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index c97d029b5c..0b00809043 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -21,4 +21,6 @@ export type { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, + ScmLocationAnalyzer, + AnalyzeOptions, } from './types'; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 8dc833abd8..961be78572 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -100,10 +100,14 @@ export type AnalyzeLocationEntityField = { description: string; }; -export interface ScmLocationAnalyzer { - analyze( - owner: string, - repo: string, - url: string, - ): Promise; -} +export type AnalyzeOptions = { + url: string; + catalogFilename?: string; +}; +/** @public */ +export type ScmLocationAnalyzer = { + /** The integration type this location analyzer can work with */ + getIntegrationType(): string; + /** This function is responsible to figure out if the catalog file is already present in the repository */ + analyze(options: AnalyzeOptions): Promise; +}; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index f1d29f015c..97379ac491 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -60,7 +60,7 @@ import { yamlPlaceholderResolver, } from '../modules/core/PlaceholderProcessor'; import { defaultEntityDataParser } from '../modules/util/parse'; -import { LocationAnalyzer } from '../ingestion/types'; +import { LocationAnalyzer, ScmLocationAnalyzer } from '../ingestion/types'; import { CatalogProcessingEngine } from '../processing'; import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase'; import { applyDatabaseMigrations } from '../database/migrations'; @@ -120,6 +120,9 @@ export type CatalogEnvironment = { * after the processors' pre-processing steps. All policies are given the * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. + * - Location analyzers can be added. These are responsible to analyze the + * the existence of a catalog-info.yaml file int he provided git repository + * when you use the /catalog-import page with a repository url. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace * dollar-prefixed entries with their actual values (like $file). @@ -140,6 +143,7 @@ export class CatalogBuilder { private fieldFormatValidators: Partial; private entityProviders: EntityProvider[]; private processors: CatalogProcessor[]; + private locationAnalyzers: ScmLocationAnalyzer[]; private processorsReplace: boolean; private parser: CatalogProcessorParser | undefined; private onProcessingError?: (event: { @@ -170,6 +174,7 @@ export class CatalogBuilder { this.fieldFormatValidators = {}; this.entityProviders = []; this.processors = []; + this.locationAnalyzers = []; this.processorsReplace = false; this.parser = undefined; this.permissionRules = Object.values(catalogPermissionRules); @@ -340,6 +345,20 @@ export class CatalogBuilder { ]; } + /** + * Adds Location Analyzers. These are responsible for figuring out + * if the repository already contains a catalog-info.yaml file when + * you register a repostiroy in the /catalog-import page + * + * @param locationAnalyzers - One or more location analyzers + */ + addLocationAnalyzers( + ...analyzers: Array> + ): CatalogBuilder { + this.locationAnalyzers.push(...analyzers.flat()); + return this; + } + /** * Sets up the catalog to use a custom parser for entity data. * @@ -484,7 +503,7 @@ export class CatalogBuilder { const locationAnalyzer = this.locationAnalyzer ?? - new RepoLocationAnalyzer(logger, integrations, this.env.discovery); + new RepoLocationAnalyzer(logger, integrations, this.locationAnalyzers); const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, diff --git a/yarn.lock b/yarn.lock index c3155dbfee..718736394b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4550,6 +4550,7 @@ __metadata: "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@octokit/graphql": ^5.0.0 + "@octokit/rest": ^19.0.4 "@types/lodash": ^4.14.151 lodash: ^4.17.21 msw: ^0.47.0 From 7022aebf35b0c3def2b84593b48c6c6290fd79b7 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:32:42 +0200 Subject: [PATCH 016/160] add changeset Signed-off-by: Kiss Miklos --- .changeset/moody-carrots-shout.md | 5 +++ .../src/lib/github.ts | 33 ------------------- yarn.lock | 4 ++- 3 files changed, 8 insertions(+), 34 deletions(-) create mode 100644 .changeset/moody-carrots-shout.md diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md new file mode 100644 index 0000000000..a1c357041f --- /dev/null +++ b/.changeset/moody-carrots-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given url that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index ec904a52e0..6a944af330 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -308,39 +308,6 @@ export async function getOrganizationRepositories( return { repositories }; } -export async function getRepository( - client: typeof graphql, - org: string, - name: string, -): Promise { - const query = ` - query repositories($org: String!, $name: String!) { - repository(name: $name, owner: $org) { - name - url - isArchived - repositoryTopics(first: 100) { - nodes { - ... on RepositoryTopic { - topic { - name - } - } - } - } - defaultBranchRef { - name - } - } - }`; - - const repository: Repository = await client(query, { - org, - name, - }); - return repository; -} - /** * Gets all the users out of a GitHub organization. * diff --git a/yarn.lock b/yarn.lock index 718736394b..a418c7e297 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4541,6 +4541,7 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -4552,6 +4553,7 @@ __metadata: "@octokit/graphql": ^5.0.0 "@octokit/rest": ^19.0.4 "@types/lodash": ^4.14.151 + git-url-parse: ^13.1.0 lodash: ^4.17.21 msw: ^0.47.0 node-fetch: ^2.6.7 @@ -23512,7 +23514,7 @@ __metadata: languageName: node linkType: hard -"git-url-parse@npm:^13.0.0": +"git-url-parse@npm:^13.0.0, git-url-parse@npm:^13.1.0": version: 13.1.0 resolution: "git-url-parse@npm:13.1.0" dependencies: From 2f5b97df6c30087e0ba353424ea7b0749d6f3b1d Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:38:51 +0200 Subject: [PATCH 017/160] get correct integration from config Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 218d56410d..7ab92565ce 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -15,10 +15,7 @@ */ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { - GitHubIntegration, - ScmIntegrationsGroup, -} from '@backstage/integration'; +import { ScmIntegrations } from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; @@ -28,18 +25,18 @@ import { ScmLocationAnalyzer, } from '@backstage/plugin-catalog-backend'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; export type GitHubLocationAnalyzerOptions = { - integrations: ScmIntegrationsGroup; - catalogFilename?: string; + config: Config; discovery: PluginEndpointDiscovery; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { private readonly catalogClient: CatalogApi; - private readonly integrations: ScmIntegrationsGroup; + private readonly config: Config; constructor(options: GitHubLocationAnalyzerOptions) { - this.integrations = options.integrations; + this.config = options.config; this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); } getIntegrationType() { @@ -55,7 +52,9 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { const query = `filename:${catalogFile} repo:${owner}/${repo}`; - const integration = this.integrations.byUrl(url); + const integration = ScmIntegrations.fromConfig(this.config).github.byUrl( + url, + ); if (!integration) { throw new Error('Make sure you have a GitHub integration configured'); } From 3cbe42d8a97a331444286c6b05b77cad4642ebe7 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:42:11 +0200 Subject: [PATCH 018/160] fix tests Signed-off-by: Kiss Miklos --- .../analyzers/GitHubLocationAnalyzer.test.ts | 21 ++++++++----------- .../src/lib/index.ts | 1 - 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index 5ceb9cf85c..f4996f232a 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -37,7 +37,7 @@ import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; -import { GitHubIntegration } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; const server = setupServer(); @@ -46,17 +46,14 @@ describe('GitHubLocationAnalyzer', () => { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), getExternalBaseUrl: jest.fn(), }; - const integrations = { - list: jest.fn(), - byHost: jest.fn(), - byUrl: () => - new GitHubIntegration({ + const config = new ConfigReader({ + integrations: { + github: { host: 'h.com', - apiBaseUrl: 'a', - rawBaseUrl: 'r', token: 't', - }), - }; + }, + }, + }); setupRequestMockHandlers(server); @@ -117,7 +114,7 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integrations, + config, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', @@ -142,7 +139,7 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integrations, + config, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 28cbe898d6..26ec3e8516 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -20,7 +20,6 @@ export { getOrganizationRepositories, getOrganizationTeams, getOrganizationUsers, - getRepository, } from './github'; export { assignGroupsToUsers, buildOrgHierarchy } from './org'; export { parseGitHubOrgUrl } from './util'; From e74ac1c708e09bf4fff5020a017ab3244f066c65 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 30 Sep 2022 10:34:10 +0200 Subject: [PATCH 019/160] add analyzers to the CatalogBuilder Signed-off-by: Kiss Miklos --- .changeset/moody-carrots-shout.md | 2 +- .../api-report.md | 22 +++++++++++++++++++ .../src/analyzers/GitHubLocationAnalyzer.ts | 2 ++ .../src/index.ts | 1 + plugins/catalog-backend/api-report.md | 16 +++++++++++++- plugins/catalog-backend/package.json | 1 - .../src/ingestion/LocationAnalyzer.ts | 11 +++------- .../catalog-backend/src/ingestion/types.ts | 2 ++ .../src/service/CatalogBuilder.ts | 7 +----- .../src/service/CatalogPlugin.ts | 4 ---- .../src/service/standaloneServer.ts | 1 - 11 files changed, 47 insertions(+), 22 deletions(-) diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md index a1c357041f..1b1555a5dd 100644 --- a/.changeset/moody-carrots-shout.md +++ b/.changeset/moody-carrots-shout.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-github': minor --- -Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given url that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 8e8055bafc..0d6c8c015f 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-backend'; +import { AnalyzeOptions } from '@backstage/plugin-catalog-backend'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; @@ -13,7 +15,9 @@ import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; import { TaskScheduleDefinition } from '@backstage/backend-tasks'; @@ -70,6 +74,24 @@ export type GithubEntityProviderCatalogModuleOptions = { schedule?: TaskScheduleDefinition; }; +// @public (undocumented) +export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { + constructor(options: GitHubLocationAnalyzerOptions); + // (undocumented) + analyze({ + url, + catalogFilename, + }: AnalyzeOptions): Promise; + // (undocumented) + getIntegrationType(): string; +} + +// @public (undocumented) +export type GitHubLocationAnalyzerOptions = { + config: Config; + discovery: PluginEndpointDiscovery; +}; + // @public export type GithubMultiOrgConfig = Array<{ name: string; diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 7ab92565ce..a043b894cd 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -27,10 +27,12 @@ import { import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Config } from '@backstage/config'; +/** @public */ export type GitHubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; }; +/** @public */ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { private readonly catalogClient: CatalogApi; private readonly config: Config; diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index e856277873..ca7c72f11b 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -30,3 +30,4 @@ export type { GithubMultiOrgConfig } from './lib'; export { githubEntityProviderCatalogModule } from './module'; export type { GithubEntityProviderCatalogModuleOptions } from './module'; export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; +export type { GitHubLocationAnalyzerOptions } from './analyzers/GitHubLocationAnalyzer'; diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index bf53badd87..15cb23d1aa 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -87,6 +87,12 @@ export type AnalyzeLocationResponse = { generateEntities: AnalyzeLocationGenerateEntity[]; }; +// @public (undocumented) +export type AnalyzeOptions = { + url: string; + catalogFilename?: string; +}; + // @public (undocumented) export class AnnotateLocationEntityProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry }); @@ -134,6 +140,9 @@ export class CatalogBuilder { addEntityProvider( ...providers: Array> ): CatalogBuilder; + addLocationAnalyzers( + ...analyzers: Array> + ): CatalogBuilder; // @alpha addPermissionRules( ...permissionRules: Array< @@ -219,7 +228,6 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: PluginEndpointDiscovery; }; // @alpha @@ -533,6 +541,12 @@ export type ProcessingIntervalFunction = () => number; export { processingResult }; +// @public (undocumented) +export type ScmLocationAnalyzer = { + getIntegrationType(): string; + analyze(options: AnalyzeOptions): Promise; +}; + // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { constructor(options: { reader: UrlReader; logger: Logger }); diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8232f1d05a..f5ab216a7f 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -47,7 +47,6 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", - "@octokit/rest": "^19.0.4", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 7992903b65..11f9799759 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -17,10 +17,7 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; -import { - GitHubIntegration, - ScmIntegrationRegistry, -} from '@backstage/integration'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, @@ -45,9 +42,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { - const integration = this.scmIntegrations.byUrl( - request.location.target, - ) as GitHubIntegration; + const integration = this.scmIntegrations.byUrl(request.location.target); const { owner, name } = parseGitUrl(request.location.target); let annotationPrefix; @@ -69,7 +64,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { } const analyzer = this.analyzers.find( - a => a.getIntegrationType() === integration.type, + a => a.getIntegrationType() === integration?.type, ); if (analyzer) { const existingEntityFiles = await analyzer.analyze({ diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 961be78572..b68a4681b1 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -100,10 +100,12 @@ export type AnalyzeLocationEntityField = { description: string; }; +/** @public */ export type AnalyzeOptions = { url: string; catalogFilename?: string; }; + /** @public */ export type ScmLocationAnalyzer = { /** The integration type this location analyzer can work with */ diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 97379ac491..e65625d637 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - PluginDatabaseManager, - PluginEndpointDiscovery, - UrlReader, -} from '@backstage/backend-common'; +import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, Entity, @@ -108,7 +104,6 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: PluginEndpointDiscovery; }; /** diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 3619460c4d..64e1e52494 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -22,7 +22,6 @@ import { permissionsServiceRef, urlReaderServiceRef, httpRouterServiceRef, - discoveryServiceRef, } from '@backstage/backend-plugin-api'; import { CatalogBuilder } from './CatalogBuilder'; import { @@ -79,7 +78,6 @@ export const catalogPlugin = createBackendPlugin({ permissions: permissionsServiceRef, database: databaseServiceRef, httpRouter: httpRouterServiceRef, - discovery: discoveryServiceRef, }, async init({ logger, @@ -88,7 +86,6 @@ export const catalogPlugin = createBackendPlugin({ database, permissions, httpRouter, - discovery, }) { const winstonLogger = loggerToWinstonLogger(logger); const builder = await CatalogBuilder.create({ @@ -97,7 +94,6 @@ export const catalogPlugin = createBackendPlugin({ permissions, database, logger: winstonLogger, - discovery, }); builder.addProcessor(...processingExtensions.processors); builder.addEntityProvider(...processingExtensions.entityProviders); diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 51195ccdd7..e48515fb8b 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -70,7 +70,6 @@ export async function startStandaloneServer( config, reader, permissions, - discovery, }); const catalog = await builder.build(); From 6dc9afcb16276cb2443ebe050266ad56cecd5785 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 30 Sep 2022 10:43:57 +0200 Subject: [PATCH 020/160] add yarn.lock Signed-off-by: Kiss Miklos --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a418c7e297..8fbe52e29a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4672,7 +4672,6 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/types": "workspace:^" - "@octokit/rest": ^19.0.4 "@types/core-js": ^2.5.4 "@types/express": ^4.17.6 "@types/git-url-parse": ^9.0.0 From c6e3dfee15e15049efa9c5e75edb7222ce0f45d0 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 30 Sep 2022 13:19:50 +0200 Subject: [PATCH 021/160] fix tests Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index f4996f232a..9502c47c6e 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -48,10 +48,12 @@ describe('GitHubLocationAnalyzer', () => { }; const config = new ConfigReader({ integrations: { - github: { - host: 'h.com', - token: 't', - }, + github: [ + { + host: 'h.com', + token: 't', + }, + ], }, }); From 6c2995fa1b3d02fd92c1f9abc02dc8523117e8de Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sun, 2 Oct 2022 16:11:26 +0200 Subject: [PATCH 022/160] Make it possible to enable useLongPollingLogs in scaffolder plugin Signed-off-by: bnechyporenko --- plugins/scaffolder/src/plugin.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index f9b359d93a..f64525ac8e 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -20,7 +20,7 @@ import { EntityPicker } from './components/fields/EntityPicker/EntityPicker'; import { entityNamePickerValidation } from './components/fields/EntityNamePicker'; import { EntityNamePicker } from './components/fields/EntityNamePicker/EntityNamePicker'; import { OwnerPicker } from './components/fields/OwnerPicker/OwnerPicker'; -import { repoPickerValidation } from './components/fields/RepoUrlPicker'; +import { repoPickerValidation } from './components'; import { RepoUrlPicker } from './components/fields/RepoUrlPicker/RepoUrlPicker'; import { createScaffolderFieldExtension } from './extensions'; import { @@ -30,6 +30,7 @@ import { viewTechDocRouteRef, } from './routes'; import { + configApiRef, createApiFactory, createPlugin, createRoutableExtension, @@ -50,17 +51,27 @@ export const scaffolderPlugin = createPlugin({ createApiFactory({ api: scaffolderApiRef, deps: { + configApi: configApiRef, discoveryApi: discoveryApiRef, scmIntegrationsApi: scmIntegrationsApiRef, fetchApi: fetchApiRef, identityApi: identityApiRef, }, - factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => + factory: ({ + configApi, + discoveryApi, + scmIntegrationsApi, + fetchApi, + identityApi, + }) => new ScaffolderClient({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi, + useLongPollingLogs: + configApi.getOptionalBoolean('scaffolder.useLongPollingLogs') ?? + false, }), }), ], From c8077a9f4ef86d720e70952460845eef6636c16a Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sun, 2 Oct 2022 16:17:49 +0200 Subject: [PATCH 023/160] Make it possible to enable useLongPollingLogs in scaffolder plugin Signed-off-by: bnechyporenko --- .changeset/sixty-llamas-itch.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .changeset/sixty-llamas-itch.md diff --git a/.changeset/sixty-llamas-itch.md b/.changeset/sixty-llamas-itch.md new file mode 100644 index 0000000000..6bb8e85163 --- /dev/null +++ b/.changeset/sixty-llamas-itch.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Make it possible to enable useLongPollingLogs in scaffolder plugin. + +For that you have to add to your app-config.yaml file this snippet: + +```yaml +scaffolder: + useLongPollingLogs: true +``` + +By default, the value is falsy. From 0950e2fb99f3d6493113e7d604b66347052b9935 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Sun, 2 Oct 2022 19:49:06 +0200 Subject: [PATCH 024/160] Update sixty-llamas-itch.md Updated the doc Signed-off-by: acierto --- .changeset/sixty-llamas-itch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sixty-llamas-itch.md b/.changeset/sixty-llamas-itch.md index 6bb8e85163..49e0bb3752 100644 --- a/.changeset/sixty-llamas-itch.md +++ b/.changeset/sixty-llamas-itch.md @@ -11,4 +11,4 @@ scaffolder: useLongPollingLogs: true ``` -By default, the value is falsy. +By default, the value is `false`. From 21b0e398a6106c3bc5f6c6c8716c6b27bf3bca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Kiss?= Date: Mon, 3 Oct 2022 14:54:14 +0200 Subject: [PATCH 025/160] Update plugins/catalog-backend/src/service/CatalogBuilder.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Haals Signed-off-by: Miklós Kiss --- plugins/catalog-backend/src/service/CatalogBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index e65625d637..5a737791ae 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -116,7 +116,7 @@ export type CatalogEnvironment = { * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. * - Location analyzers can be added. These are responsible to analyze the - * the existence of a catalog-info.yaml file int he provided git repository + * the existence of a catalog-info.yaml file in the provided git repository * when you use the /catalog-import page with a repository url. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace From e8357aae54bad6e2a26fa6bb52c64b50e36b7e26 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 15:10:14 +0200 Subject: [PATCH 026/160] add more details to changesets Signed-off-by: Kiss Miklos --- .changeset/curvy-pets-wash.md | 4 ++-- .changeset/fuzzy-dolls-shake.md | 20 +++++++++++++++++-- .changeset/moody-carrots-shout.md | 2 +- .../src/service/CatalogBuilder.ts | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md index 7e43228fbc..a0e9571b18 100644 --- a/.changeset/curvy-pets-wash.md +++ b/.changeset/curvy-pets-wash.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend': minor --- -Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials +Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md index 92380b37ec..0148a703a4 100644 --- a/.changeset/fuzzy-dolls-shake.md +++ b/.changeset/fuzzy-dolls-shake.md @@ -1,5 +1,21 @@ --- -'@backstage/plugin-catalog-import': minor +'@backstage/plugin-catalog-import': patch --- -Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials +**Breaking** +Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + +Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + +```ts +// catalog.ts +import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; +... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); +... +``` diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md index 1b1555a5dd..486590e0ba 100644 --- a/.changeset/moody-carrots-shout.md +++ b/.changeset/moody-carrots-shout.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-github': minor +'@backstage/plugin-catalog-backend-module-github': patch --- Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 5a737791ae..deed865314 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -343,7 +343,7 @@ export class CatalogBuilder { /** * Adds Location Analyzers. These are responsible for figuring out * if the repository already contains a catalog-info.yaml file when - * you register a repostiroy in the /catalog-import page + * you register a repository in the /catalog-import page * * @param locationAnalyzers - One or more location analyzers */ From 41de015847824dff329e5beb3f276b8a26d19040 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 16:34:53 +0200 Subject: [PATCH 027/160] make breaking change a minor change Signed-off-by: Kiss Miklos --- .changeset/fuzzy-dolls-shake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md index 0148a703a4..35ebcf6103 100644 --- a/.changeset/fuzzy-dolls-shake.md +++ b/.changeset/fuzzy-dolls-shake.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-import': minor --- **Breaking** From 290a8cd57730a62519a2528a9e5789f4b2202776 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 17:28:34 +0200 Subject: [PATCH 028/160] use the existing versions Signed-off-by: Kiss Miklos --- plugins/catalog-backend-module-github/package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 950c7723e8..f99b951149 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -45,8 +45,8 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/graphql": "^5.0.0", - "@octokit/rest": "^19.0.4", - "git-url-parse": "^13.1.0", + "@octokit/rest": "^19.0.3", + "git-url-parse": "^13.0.0", "lodash": "^4.17.21", "msw": "^0.47.0", "node-fetch": "^2.6.7", diff --git a/yarn.lock b/yarn.lock index 8fbe52e29a..5563c9ffbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4551,9 +4551,9 @@ __metadata: "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@octokit/graphql": ^5.0.0 - "@octokit/rest": ^19.0.4 + "@octokit/rest": ^19.0.3 "@types/lodash": ^4.14.151 - git-url-parse: ^13.1.0 + git-url-parse: ^13.0.0 lodash: ^4.17.21 msw: ^0.47.0 node-fetch: ^2.6.7 @@ -11580,7 +11580,7 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^19.0.3, @octokit/rest@npm:^19.0.4": +"@octokit/rest@npm:^19.0.3": version: 19.0.4 resolution: "@octokit/rest@npm:19.0.4" dependencies: @@ -23513,7 +23513,7 @@ __metadata: languageName: node linkType: hard -"git-url-parse@npm:^13.0.0, git-url-parse@npm:^13.1.0": +"git-url-parse@npm:^13.0.0": version: 13.1.0 resolution: "git-url-parse@npm:13.1.0" dependencies: From 1effb063c2d073f458dfbf4a43d56dfcb144f30b Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 17:37:27 +0200 Subject: [PATCH 029/160] more general description comment Signed-off-by: Kiss Miklos --- .../catalog-backend/src/service/CatalogBuilder.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index deed865314..d5bc7057dd 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -115,9 +115,10 @@ export type CatalogEnvironment = { * after the processors' pre-processing steps. All policies are given the * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. - * - Location analyzers can be added. These are responsible to analyze the - * the existence of a catalog-info.yaml file in the provided git repository - * when you use the /catalog-import page with a repository url. + * - Location analyzers can be added. These are responsible for analyzing + * repositories when onboarding them into the catalog, by finding + * catalog-info.yaml files and other artifacts that can help automatically + * register or create catalog data on the user's behalf. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace * dollar-prefixed entries with their actual values (like $file). @@ -341,9 +342,10 @@ export class CatalogBuilder { } /** - * Adds Location Analyzers. These are responsible for figuring out - * if the repository already contains a catalog-info.yaml file when - * you register a repository in the /catalog-import page + * Adds Location Analyzers. These are responsible for analyzing + * repositories when onboarding them into the catalog, by finding + * catalog-info.yaml files and other artifacts that can help automatically + * register or create catalog data on the user's behalf. * * @param locationAnalyzers - One or more location analyzers */ From 99cf6b11b1c6ae9cd2223cd4c99c39acc029d817 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 17:38:33 +0200 Subject: [PATCH 030/160] don't use any Signed-off-by: Kiss Miklos --- plugins/catalog-import/src/api/CatalogImportClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 5dd3a3178f..d019b40045 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -145,7 +145,7 @@ export class CatalogImportClient implements CatalogImportApi { type: 'repository', integrationType: 'github', url: url, - generatedEntities: analyzation.generateEntities.map((x: any) => x.entity), + generatedEntities: analyzation.generateEntities.map(x => x.entity), }; } From a18c07d308616660bea9b38a53aa5fec0021dc11 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 19:24:36 +0200 Subject: [PATCH 031/160] make the return of analyze an object Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.test.ts | 6 +++--- .../src/analyzers/GitHubLocationAnalyzer.ts | 9 +++------ .../catalog-backend/src/ingestion/LocationAnalyzer.ts | 6 +++--- plugins/catalog-backend/src/ingestion/types.ts | 6 ++++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index 9502c47c6e..0dcc131502 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -122,8 +122,8 @@ describe('GitHubLocationAnalyzer', () => { url: 'https://github.com/foo/bar', }); - expect(result[0].isRegistered).toBeFalsy(); - expect(result[0].location).toEqual({ + expect(result.existing[0].isRegistered).toBeFalsy(); + expect(result.existing[0].location).toEqual({ type: 'url', target: 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', @@ -148,7 +148,7 @@ describe('GitHubLocationAnalyzer', () => { catalogFilename: 'anvil.yaml', }); - expect(result[0].location).toEqual({ + expect(result.existing[0].location).toEqual({ type: 'url', target: 'https://github.com/foo/bar/blob/my_default_branch/anvil.yaml', }); diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index a043b894cd..085f82de49 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -44,10 +44,7 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { getIntegrationType() { return 'github'; } - async analyze({ - url, - catalogFilename, - }: AnalyzeOptions): Promise { + async analyze({ url, catalogFilename }: AnalyzeOptions) { const { owner, name: repo } = parseGitUrl(url); const catalogFile = catalogFilename || 'catalog-info.yaml'; @@ -98,8 +95,8 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { }), ); - return result.flat(); + return { existing: result.flat() }; } - return []; + return { existing: [] }; } } diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 11f9799759..b863f00f2c 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -67,15 +67,15 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { a => a.getIntegrationType() === integration?.type, ); if (analyzer) { - const existingEntityFiles = await analyzer.analyze({ + const analyzerResult = await analyzer.analyze({ url: request.location.target, }); - if (existingEntityFiles.length > 0) { + if (analyzerResult.existing.length > 0) { this.logger.debug( `entity for ${request.location.target} already exists.`, ); return { - existingEntityFiles, + existingEntityFiles: analyzerResult.existing, generateEntities: [], }; } diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index b68a4681b1..40b4574f78 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -110,6 +110,8 @@ export type AnalyzeOptions = { export type ScmLocationAnalyzer = { /** The integration type this location analyzer can work with */ getIntegrationType(): string; - /** This function is responsible to figure out if the catalog file is already present in the repository */ - analyze(options: AnalyzeOptions): Promise; + /** This function can return an array of already existing entities */ + analyze(options: AnalyzeOptions): Promise<{ + existing: AnalyzeLocationExistingEntity[]; + }>; }; From ce865b69cf6425742517cd8ce86ed013fe71c031 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 19:55:50 +0200 Subject: [PATCH 032/160] invert the control on the analyzer support Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.ts | 7 ++++--- plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts | 4 ++-- plugins/catalog-backend/src/ingestion/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 085f82de49..d1112d2cc0 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -20,7 +20,6 @@ import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; import { - AnalyzeLocationExistingEntity, AnalyzeOptions, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-backend'; @@ -41,8 +40,10 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { this.config = options.config; this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); } - getIntegrationType() { - return 'github'; + supports(url: string) { + const integrations = ScmIntegrations.fromConfig(this.config); + const integration = integrations.byUrl(url); + return integration?.type === 'github'; } async analyze({ url, catalogFilename }: AnalyzeOptions) { const { owner, name: repo } = parseGitUrl(url); diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index b863f00f2c..3dc08a81c3 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -63,8 +63,8 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; } - const analyzer = this.analyzers.find( - a => a.getIntegrationType() === integration?.type, + const analyzer = this.analyzers.find(a => + a.supports(request.location.target), ); if (analyzer) { const analyzerResult = await analyzer.analyze({ diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 40b4574f78..c5237af6d3 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -108,8 +108,8 @@ export type AnalyzeOptions = { /** @public */ export type ScmLocationAnalyzer = { - /** The integration type this location analyzer can work with */ - getIntegrationType(): string; + /** The method that decides if this analyzer can work with the provided url */ + supports(url: string): boolean; /** This function can return an array of already existing entities */ analyze(options: AnalyzeOptions): Promise<{ existing: AnalyzeLocationExistingEntity[]; From 41568be052894270a0f4dd697cc1035fb13cf56d Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 4 Oct 2022 00:50:01 +0200 Subject: [PATCH 033/160] generate api-reports Signed-off-by: Kiss Miklos --- .../api-report.md | 18 ++++++++++++------ plugins/catalog-backend/api-report.md | 6 ++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 0d6c8c015f..c89a34c259 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -3,12 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-backend'; import { AnalyzeOptions } from '@backstage/plugin-catalog-backend'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; +import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -78,12 +78,18 @@ export type GithubEntityProviderCatalogModuleOptions = { export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { constructor(options: GitHubLocationAnalyzerOptions); // (undocumented) - analyze({ - url, - catalogFilename, - }: AnalyzeOptions): Promise; + analyze({ url, catalogFilename }: AnalyzeOptions): Promise<{ + existing: { + location: { + type: string; + target: string; + }; + isRegistered: boolean; + entity: Entity; + }[]; + }>; // (undocumented) - getIntegrationType(): string; + supports(url: string): boolean; } // @public (undocumented) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 15cb23d1aa..ede024e296 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -543,8 +543,10 @@ export { processingResult }; // @public (undocumented) export type ScmLocationAnalyzer = { - getIntegrationType(): string; - analyze(options: AnalyzeOptions): Promise; + supports(url: string): boolean; + analyze(options: AnalyzeOptions): Promise<{ + existing: AnalyzeLocationExistingEntity[]; + }>; }; // @public (undocumented) From e9b41910717694bd30c0cdf6960068b2a08c7013 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Wed, 28 Sep 2022 15:07:28 +0100 Subject: [PATCH 034/160] Added parameter scheamas to permission rules Signed-off-by: Harry Hogg --- .../src/permissions/rules/createPropertyRule.ts | 5 +++++ .../catalog-backend/src/permissions/rules/hasAnnotation.ts | 5 +++++ plugins/catalog-backend/src/permissions/rules/hasLabel.ts | 2 ++ .../catalog-backend/src/permissions/rules/isEntityKind.ts | 2 ++ .../catalog-backend/src/permissions/rules/isEntityOwner.ts | 2 ++ plugins/catalog-backend/src/service/createRouter.test.ts | 2 ++ .../src/service/PermissionIntegrationClient.test.ts | 3 +++ .../src/integration/createConditionExports.test.ts | 3 +++ .../src/integration/createConditionFactory.test.ts | 2 ++ .../src/integration/createConditionTransformer.test.ts | 3 +++ .../integration/createPermissionIntegrationRouter.test.ts | 3 +++ plugins/permission-node/src/integration/util.test.ts | 3 +++ plugins/permission-node/src/types.ts | 6 ++++++ plugins/playlist-backend/package.json | 3 ++- plugins/playlist-backend/src/permissions/rules.ts | 3 +++ yarn.lock | 1 + 16 files changed, 47 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 57de5eb922..12725c1fcd 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -18,12 +18,17 @@ import { get } from 'lodash'; import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; +import { z } from 'zod'; export const createPropertyRule = (propertyType: 'metadata' | 'spec') => createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + schema: z.tuple([ + z.string().describe('Property name'), + z.string().optional().describe('Property value'), + ]), apply: (resource: Entity, key: string, value?: string) => { const foundValue = get(resource[propertyType], key); diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 22dbd307a6..4042c34636 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -31,6 +32,10 @@ export const hasAnnotation = createCatalogPermissionRule({ description: 'Allow entities which are annotated with the specified annotation', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + schema: z.tuple([ + z.string().describe('Annotation name'), + z.string().optional().describe('Annotation value'), + ]), apply: (resource: Entity, annotation: string, value?: string) => !!resource.metadata.annotations?.hasOwnProperty(annotation) && (value === undefined diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 9f37cbfb59..3d1a18f8b3 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -27,6 +28,7 @@ export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + schema: z.tuple([z.string().describe('Label name')]), apply: (resource: Entity, label: string) => !!resource.metadata.labels?.hasOwnProperty(label), toQuery: (label: string) => ({ diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 3bd600e8fe..70467558dd 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; @@ -27,6 +28,7 @@ export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + schema: z.tuple([z.array(z.string().describe('List of entity kinds'))]), apply(resource: Entity, kinds: string[]) { const resourceKind = resource.kind.toLocaleLowerCase('en-US'); return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 5403da2310..c0cc7c9911 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -16,6 +16,7 @@ import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -28,6 +29,7 @@ export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + schema: z.tuple([z.array(z.string().describe('List of owners'))]), apply: (resource: Entity, claims: string[]) => { if (!resource.relations) { return false; diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 16a36eb8c4..163e5e0747 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -36,6 +36,7 @@ import { } from '@backstage/plugin-permission-node'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { CatalogProcessingOrchestrator } from '../processing/types'; +import { z } from 'zod'; describe('createRouter readonly disabled', () => { let entitiesCatalog: jest.Mocked; @@ -695,6 +696,7 @@ describe('NextRouter permissioning', () => { name: 'FAKE_RULE', description: 'fake rule', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + schema: z.tuple([]), apply: () => true, toQuery: () => ({ key: '', values: [] }), }); diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index 4c50e1646f..6c05a825ab 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -30,6 +30,7 @@ import { createPermissionRule, } from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; +import { z } from 'zod'; describe('PermissionIntegrationClient', () => { describe('applyConditions', () => { @@ -279,6 +280,7 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_1', description: 'Test rule 1', resourceType: 'test-resource', + schema: z.tuple([z.enum(['yes', 'no'])]), apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', toQuery: () => { throw new Error('Not implemented'); @@ -288,6 +290,7 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_2', description: 'Test rule 2', resourceType: 'test-resource', + schema: z.tuple([z.enum(['yes', 'no'])]), apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', toQuery: () => { throw new Error('Not implemented'); diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index 54d5652996..ed0ab49b5c 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -18,6 +18,7 @@ import { AuthorizeResult, createPermission, } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; import { createConditionExports } from './createConditionExports'; import { createPermissionRule } from './createPermissionRule'; @@ -30,6 +31,7 @@ const testIntegration = () => name: 'testRule1', description: 'Test rule 1', resourceType: 'test-resource', + schema: z.tuple([z.string(), z.number()]), apply: jest.fn( (_resource: any, _firstParam: string, _secondParam: number) => true, ), @@ -42,6 +44,7 @@ const testIntegration = () => name: 'testRule2', description: 'Test rule 2', resourceType: 'test-resource', + schema: z.tuple([z.object({})]), apply: jest.fn((_resource: any, _firstParam: object) => false), toQuery: jest.fn((firstParam: object) => ({ query: 'testRule2', diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 1941b5ac0b..433d37b3f6 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { z } from 'zod'; import { createConditionFactory } from './createConditionFactory'; import { createPermissionRule } from './createPermissionRule'; @@ -22,6 +23,7 @@ describe('createConditionFactory', () => { name: 'test-rule', description: 'test-description', resourceType: 'test-resource', + schema: z.tuple([]), apply: jest.fn(), toQuery: jest.fn(), }); diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 917449561d..421bbcb2f4 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -18,6 +18,7 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; import { createConditionTransformer } from './createConditionTransformer'; import { createPermissionRule } from './createPermissionRule'; @@ -26,6 +27,7 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', + schema: z.tuple([]), apply: jest.fn(), toQuery: jest.fn( (firstParam: string, secondParam: number) => @@ -36,6 +38,7 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', + schema: z.tuple([]), apply: jest.fn(), toQuery: jest.fn( (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`, diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index ac4005c487..829b1b3614 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -21,6 +21,7 @@ import { } from '@backstage/plugin-permission-common'; import express, { Express, Router } from 'express'; import request, { Response } from 'supertest'; +import { z } from 'zod'; import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter'; import { createPermissionRule } from './createPermissionRule'; @@ -39,6 +40,7 @@ const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', + schema: z.tuple([z.string(), z.number()]), apply: (_resource: any, _firstParam: string, _secondParam: number) => true, toQuery: (_firstParam: string, _secondParam: number) => ({}), }); @@ -47,6 +49,7 @@ const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', + schema: z.tuple([z.object({})]), apply: (_resource: any, _firstParam: object) => false, toQuery: (_firstParam: object) => ({}), }); diff --git a/plugins/permission-node/src/integration/util.test.ts b/plugins/permission-node/src/integration/util.test.ts index 6c1c270953..1572964a7d 100644 --- a/plugins/permission-node/src/integration/util.test.ts +++ b/plugins/permission-node/src/integration/util.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { z } from 'zod'; import { createPermissionRule } from './createPermissionRule'; import { createGetRule, @@ -30,6 +31,7 @@ describe('permission integration utils', () => { name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', + schema: z.tuple([]), apply: jest.fn(), toQuery: jest.fn(), }); @@ -38,6 +40,7 @@ describe('permission integration utils', () => { name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', + schema: z.tuple([]), apply: jest.fn(), toQuery: jest.fn(), }); diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 66b4a7b22b..7beb5bb92d 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -15,6 +15,7 @@ */ import type { PermissionCriteria } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; /** * A conditional rule that can be provided in an @@ -42,6 +43,11 @@ export type PermissionRule< description: string; resourceType: TResourceType; + /** + * A ZodSchema that documents the parameters that this rule accepts. + */ + schema: z.ZodSchema; + /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index 9e4ab86020..bee5d292b9 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -39,7 +39,8 @@ "node-fetch": "^2.6.7", "uuid": "^8.2.0", "winston": "^3.2.1", - "yn": "^4.0.0" + "yn": "^4.0.0", + "zod": "^3.11.6" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index cbf81bee65..de2d3df5e4 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -19,6 +19,7 @@ import { PLAYLIST_LIST_RESOURCE_TYPE, PlaylistMetadata, } from '@backstage/plugin-playlist-common'; +import { z } from 'zod'; import { ListPlaylistsFilter } from '../service'; @@ -32,6 +33,7 @@ const isOwner = createPlaylistPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, + schema: z.tuple([z.array(z.string().describe('List of owners'))]), apply: (list: PlaylistMetadata, userOwnershipRefs: string[]) => userOwnershipRefs.includes(list.owner), toQuery: (userOwnershipRefs: string[]) => ({ @@ -44,6 +46,7 @@ const isPublic = createPlaylistPermissionRule({ name: 'IS_PUBLIC', description: 'Should allow only if the playlist is public', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, + schema: z.tuple([]), apply: (list: PlaylistMetadata) => list.public, toQuery: () => ({ key: 'public', values: [true] }), }); diff --git a/yarn.lock b/yarn.lock index e5f10c942a..a6bb179e7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6427,6 +6427,7 @@ __metadata: uuid: ^8.2.0 winston: ^3.2.1 yn: ^4.0.0 + zod: ^3.11.6 languageName: unknown linkType: soft From 9fe88c4fab903b293a43f0bfdc87469dc05fa359 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Wed, 28 Sep 2022 15:08:43 +0100 Subject: [PATCH 035/160] Added parameter validation using the param schemas Signed-off-by: Harry Hogg --- .../createConditionTransformer.test.ts | 4 +-- .../integration/createConditionTransformer.ts | 10 +++++++- .../createPermissionIntegrationRouter.test.ts | 25 +++++++++++-------- .../createPermissionIntegrationRouter.ts | 9 ++++++- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 421bbcb2f4..e7e1e89d06 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -27,7 +27,7 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([]), + schema: z.tuple([z.string(), z.number()]), apply: jest.fn(), toQuery: jest.fn( (firstParam: string, secondParam: number) => @@ -38,7 +38,7 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([]), + schema: z.tuple([z.object({})]), apply: jest.fn(), toQuery: jest.fn( (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`, diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index 112bb30d01..b40063ec13 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { InputError } from '@backstage/errors'; import { AllOfCriteria, AnyOfCriteria, @@ -45,7 +46,14 @@ const mapConditions = ( }; } - return getRule(criteria.rule).toQuery(...criteria.params); + const rule = getRule(criteria.rule); + const result = rule.schema.safeParse(criteria.params); + + if (rule.schema && !result.success) { + throw new InputError(`Parameters to rule are invalid`, result.error); + } + + return rule.toQuery(...criteria.params); }; /** diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 829b1b3614..5649480a9c 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -40,7 +40,10 @@ const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([z.string(), z.number()]), + schema: z.tuple([ + z.string().describe('firstParam'), + z.number().describe('secondParam'), + ]), apply: (_resource: any, _firstParam: string, _secondParam: number) => true, toQuery: (_firstParam: string, _secondParam: number) => ({}), }); @@ -49,7 +52,7 @@ const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([z.object({})]), + schema: z.tuple([z.object({}).describe('firstParam')]), apply: (_resource: any, _firstParam: object) => false, toQuery: (_firstParam: object) => ({}), }); @@ -250,7 +253,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: ['a', 1], }, }, { @@ -260,7 +263,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], + params: [{}], }, }, { @@ -271,7 +274,7 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: ['a', 1], }, }, }, @@ -283,7 +286,7 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], + params: [{}], }, }, }, @@ -296,12 +299,12 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: ['a', 1], }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], + params: [{}], }, ], }, @@ -430,7 +433,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: ['a', 1], }, }, { @@ -440,7 +443,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: ['a', 1], }, }, { @@ -450,7 +453,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: ['a', 1], }, }, ], diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 28abc2f48d..6c4df1763f 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -124,7 +124,14 @@ const applyConditions = ( return !applyConditions(criteria.not, resource, getRule); } - return getRule(criteria.rule).apply(resource, ...criteria.params); + const rule = getRule(criteria.rule); + const result = rule.schema.safeParse(criteria.params); + + if (rule.schema && !result.success) { + throw new InputError(`Parameters to rule are invalid`, result.error); + } + + return rule.apply(resource, ...criteria.params); }; /** From eec3f766f220f9935bb1a182b10b886987d36769 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 29 Sep 2022 09:27:53 +0100 Subject: [PATCH 036/160] Output a JSON schema from the .well-known metadata endpoint Signed-off-by: Harry Hogg --- plugins/permission-node/package.json | 3 +- .../createPermissionIntegrationRouter.test.ts | 30 +++++++++++++++++++ .../createPermissionIntegrationRouter.ts | 2 ++ yarn.lock | 10 +++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 5461c2132e..3d6ef790f9 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -41,7 +41,8 @@ "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "zod": "^3.11.6" + "zod": "^3.11.6", + "zod-to-json-schema": "^3.18.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 5649480a9c..7d80459ba3 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -530,12 +530,42 @@ describe('createPermissionIntegrationRouter', () => { name: testRule1.name, description: testRule1.description, resourceType: testRule1.resourceType, + schema: { + $schema: 'http://json-schema.org/draft-07/schema#', + items: [ + { + description: 'firstParam', + type: 'string', + }, + { + description: 'secondParam', + type: 'number', + }, + ], + maxItems: 2, + minItems: 2, + type: 'array', + }, parameters: { count: 2 }, }, { name: testRule2.name, description: testRule2.description, resourceType: testRule2.resourceType, + schema: { + $schema: 'http://json-schema.org/draft-07/schema#', + items: [ + { + additionalProperties: false, + description: 'firstParam', + properties: {}, + type: 'object', + }, + ], + maxItems: 1, + minItems: 1, + type: 'array', + }, parameters: { count: 1 }, }, ], diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 6c4df1763f..28268b7f98 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -17,6 +17,7 @@ import express, { Response } from 'express'; import Router from 'express-promise-router'; import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; import { InputError } from '@backstage/errors'; import { errorHandler } from '@backstage/backend-common'; import { @@ -201,6 +202,7 @@ export const createPermissionIntegrationRouter = < name: rule.name, description: rule.description, resourceType: rule.resourceType, + schema: zodToJsonSchema(rule.schema), parameters: { count: rule.toQuery.length, }, diff --git a/yarn.lock b/yarn.lock index a6bb179e7c..bef0e188c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6359,6 +6359,7 @@ __metadata: msw: ^0.47.0 supertest: ^6.1.3 zod: ^3.11.6 + zod-to-json-schema: ^3.18.1 languageName: unknown linkType: soft @@ -40077,6 +40078,15 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.18.1": + version: 3.18.1 + resolution: "zod-to-json-schema@npm:3.18.1" + peerDependencies: + zod: ^3.18.0 + checksum: e55d0de83b50fbd1caa7541d037858815964477b52a9e6495496e447107386cf16e2c08b007fcfbffd7fbe069ca2c19018425a53eaee36aff5dda942d3db71f4 + languageName: node + linkType: hard + "zod@npm:^3.11.6, zod@npm:^3.9.5": version: 3.18.0 resolution: "zod@npm:3.18.0" From 46b4a72ceea915006f20f0eca38a030d4845bfe6 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 29 Sep 2022 11:08:16 +0100 Subject: [PATCH 037/160] Added changeset Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/kind-bees-suffer.md diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md new file mode 100644 index 0000000000..95899bf064 --- /dev/null +++ b/.changeset/kind-bees-suffer.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-permission-backend': minor +'@backstage/plugin-permission-node': minor +'@backstage/plugin-playlist-backend': minor +--- + +Permission rules now require a schema (ZodSchema) that details the parameters a rule expects. This is to validate the parameters given to a rule and to provide more details of a rule via the metadata endpoint From 1893c3d4da5c0e3681719f0761696dd5d5371d75 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 29 Sep 2022 11:28:18 +0100 Subject: [PATCH 038/160] Updated API reports Signed-off-by: Harry Hogg --- plugins/permission-node/api-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 702d363137..055325f5a7 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -24,6 +24,7 @@ import { PolicyDecision } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { TokenManager } from '@backstage/backend-common'; +import { z } from 'zod'; // @public export type ApplyConditionsRequest = { @@ -170,6 +171,7 @@ export type PermissionRule< name: string; description: string; resourceType: TResourceType; + schema: z.ZodSchema; apply(resource: TResource, ...params: TParams): boolean; toQuery(...params: TParams): PermissionCriteria; }; From 6d447843fa4657918f4b450ed34a129c8f479e4d Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Mon, 3 Oct 2022 10:19:47 +0100 Subject: [PATCH 039/160] Changing over permission rules params API to accept a single object Signed-off-by: Harry Hogg --- .../rules/createPropertyRule.test.ts | 52 +++-- .../permissions/rules/createPropertyRule.ts | 16 +- .../permissions/rules/hasAnnotation.test.ts | 41 ++-- .../src/permissions/rules/hasAnnotation.ts | 16 +- .../src/permissions/rules/hasLabel.test.ts | 16 +- .../src/permissions/rules/hasLabel.ts | 9 +- .../permissions/rules/isEntityKind.test.ts | 18 +- .../src/permissions/rules/isEntityKind.ts | 9 +- .../permissions/rules/isEntityOwner.test.ts | 30 ++- .../src/permissions/rules/isEntityOwner.ts | 10 +- .../src/permissions/rules/util.ts | 5 +- .../service/AuthorizedEntitiesCatalog.test.ts | 8 +- .../src/service/createRouter.test.ts | 8 +- .../PermissionIntegrationClient.test.ts | 41 ++-- .../src/PermissionClient.test.ts | 4 +- .../permission-common/src/PermissionClient.ts | 2 +- plugins/permission-common/src/types/api.ts | 2 +- .../createConditionExports.test.ts | 55 ++++-- .../src/integration/createConditionExports.ts | 2 +- .../createConditionFactory.test.ts | 18 +- .../src/integration/createConditionFactory.ts | 4 +- .../createConditionTransformer.test.ts | 99 +++++++--- .../integration/createConditionTransformer.ts | 2 +- .../createPermissionIntegrationRouter.test.ts | 180 ++++++++++++------ .../createPermissionIntegrationRouter.ts | 4 +- .../src/integration/createPermissionRule.ts | 4 +- .../src/integration/util.test.ts | 4 +- plugins/permission-node/src/types.ts | 12 +- .../DefaultPlaylistPermissionPolicy.test.ts | 32 ++-- .../DefaultPlaylistPermissionPolicy.ts | 10 +- .../playlist-backend/src/permissions/rules.ts | 17 +- 31 files changed, 495 insertions(+), 235 deletions(-) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts index cb11add9ed..5b34c98ca6 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts @@ -41,7 +41,9 @@ describe('createPropertyRule', () => { name: 'test-component', }, }, - 'org.name', + { + key: 'org.name', + }, ), ).toBe(false); }); @@ -57,7 +59,9 @@ describe('createPropertyRule', () => { tags: [], }, }, - 'tags', + { + key: 'tags', + }, ), ).toBe(false); }); @@ -75,7 +79,9 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', + { + key: 'org.name', + }, ), ).toBe(true); }); @@ -91,7 +97,9 @@ describe('createPropertyRule', () => { tags: ['java'], }, }, - 'tags', + { + key: 'tags', + }, ), ).toBe(true); }); @@ -108,8 +116,10 @@ describe('createPropertyRule', () => { name: 'test-component', }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(false); }); @@ -127,8 +137,10 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(false); }); @@ -144,8 +156,10 @@ describe('createPropertyRule', () => { tags: ['java'], }, }, - 'tags', - 'python', + { + key: 'tags', + value: 'python', + }, ), ).toBe(false); }); @@ -163,8 +177,10 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(true); }); @@ -180,8 +196,10 @@ describe('createPropertyRule', () => { tags: ['java', 'java11'], }, }, - 'tags', - 'java', + { + key: 'tags', + value: 'java', + }, ), ).toBe(true); }); @@ -190,7 +208,11 @@ describe('createPropertyRule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(toQuery('backstage.io/test-component')).toEqual({ + expect( + toQuery({ + key: 'backstage.io/test-component', + }), + ).toEqual({ key: 'metadata.backstage.io/test-component', }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 12725c1fcd..da487c112c 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -15,7 +15,6 @@ */ import { get } from 'lodash'; -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; import { z } from 'zod'; @@ -25,11 +24,14 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') => name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.tuple([ - z.string().describe('Property name'), - z.string().optional().describe('Property value'), - ]), - apply: (resource: Entity, key: string, value?: string) => { + schema: z.object({ + key: z.string().describe(`The key of the ${propertyType} to match on`), + value: z + .string() + .optional() + .describe(`Optional value of the ${propertyType} to match on`), + }), + apply: (resource, { key, value }) => { const foundValue = get(resource[propertyType], key); if (Array.isArray(foundValue)) { @@ -43,7 +45,7 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') => } return !!foundValue; }, - toQuery: (key: string, value?: string) => ({ + toQuery: ({ key, value }) => ({ key: `${propertyType}.${key}`, ...(value !== undefined && { values: [value] }), }), diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts index 609114be3d..73fce20a71 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts @@ -31,7 +31,9 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(false); }); @@ -46,7 +48,9 @@ describe('hasAnnotation permission rule', () => { name: 'test-component', }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(false); expect( @@ -58,8 +62,10 @@ describe('hasAnnotation permission rule', () => { name: 'test-component', }, }, - 'backstage.io/test-annotation', - 'some value', + { + annotation: 'backstage.io/test-annotation', + value: 'some value', + }, ), ).toEqual(false); }); @@ -78,7 +84,9 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(true); }); @@ -97,8 +105,10 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', - 'baz', + { + annotation: 'backstage.io/test-annotation', + value: 'baz', + }, ), ).toEqual(false); }); @@ -117,8 +127,10 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', - 'bar', + { + annotation: 'backstage.io/test-annotation', + value: 'bar', + }, ), ).toEqual(true); }); @@ -126,7 +138,11 @@ describe('hasAnnotation permission rule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(hasAnnotation.toQuery('backstage.io/test-annotation')).toEqual({ + expect( + hasAnnotation.toQuery({ + annotation: 'backstage.io/test-annotation', + }), + ).toEqual({ key: 'metadata.annotations.backstage.io/test-annotation', }); }); @@ -134,7 +150,10 @@ describe('hasAnnotation permission rule', () => { it('returns an appropriate catalog-backend filter with values', () => { expect( - hasAnnotation.toQuery('backstage.io/test-annotation', 'foo'), + hasAnnotation.toQuery({ + annotation: 'backstage.io/test-annotation', + value: 'foo', + }), ).toEqual({ key: 'metadata.annotations.backstage.io/test-annotation', values: ['foo'], diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 4042c34636..9651e9d925 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; @@ -32,16 +31,19 @@ export const hasAnnotation = createCatalogPermissionRule({ description: 'Allow entities which are annotated with the specified annotation', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.tuple([ - z.string().describe('Annotation name'), - z.string().optional().describe('Annotation value'), - ]), - apply: (resource: Entity, annotation: string, value?: string) => + schema: z.object({ + annotation: z.string().describe('The name of the annotation to match on'), + value: z + .string() + .optional() + .describe('Optional value of the annotation to match on'), + }), + apply: (resource, { annotation, value }) => !!resource.metadata.annotations?.hasOwnProperty(annotation) && (value === undefined ? true : resource.metadata.annotations?.[annotation] === value), - toQuery: (annotation: string, value?: string) => + toQuery: ({ annotation, value }) => value === undefined ? { key: `metadata.annotations.${annotation}`, diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts index a1b9cb5ad0..8aa43402c5 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts @@ -31,7 +31,9 @@ describe('hasLabel permission rule', () => { }, }, }, - 'backstage.io/testlabel', + { + label: 'backstage.io/testlabel', + }, ), ).toEqual(false); }); @@ -46,7 +48,9 @@ describe('hasLabel permission rule', () => { name: 'test-component', }, }, - 'backstage.io/testlabel', + { + label: 'backstage.io/testlabel', + }, ), ).toEqual(false); }); @@ -65,7 +69,7 @@ describe('hasLabel permission rule', () => { }, }, }, - 'backstage.io/testlabel', + { label: 'backstage.io/testlabel' }, ), ).toEqual(true); }); @@ -73,7 +77,11 @@ describe('hasLabel permission rule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(hasLabel.toQuery('backstage.io/testlabel')).toEqual({ + expect( + hasLabel.toQuery({ + label: 'backstage.io/testlabel', + }), + ).toEqual({ key: 'metadata.labels.backstage.io/testlabel', }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 3d1a18f8b3..fbad7e42d0 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; @@ -28,10 +27,12 @@ export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.tuple([z.string().describe('Label name')]), - apply: (resource: Entity, label: string) => + schema: z.object({ + label: z.string().describe('Name of the label'), + }), + apply: (resource, { label }) => !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: (label: string) => ({ + toQuery: ({ label }) => ({ key: `metadata.labels.${label}`, }), }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts index e11e102728..28983f6bec 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts @@ -27,7 +27,11 @@ describe('isEntityKind', () => { name: 'some-component', }, }; - expect(isEntityKind.apply(component, ['b'])).toBe(true); + expect( + isEntityKind.apply(component, { + kinds: ['b'], + }), + ).toBe(true); }); it('returns false when entity is not the correct kind', () => { @@ -38,13 +42,21 @@ describe('isEntityKind', () => { name: 'some-component', }, }; - expect(isEntityKind.apply(component, ['c'])).toBe(false); + expect( + isEntityKind.apply(component, { + kinds: ['c'], + }), + ).toBe(false); }); }); describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(isEntityKind.toQuery(['b'])).toEqual({ + expect( + isEntityKind.toQuery({ + kinds: ['b'], + }), + ).toEqual({ key: 'kind', values: ['b'], }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 70467558dd..8c16f4c5a1 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { z } from 'zod'; import { EntitiesSearchFilter } from '../../catalog/types'; @@ -28,12 +27,14 @@ export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.tuple([z.array(z.string().describe('List of entity kinds'))]), - apply(resource: Entity, kinds: string[]) { + schema: z.object({ + kinds: z.array(z.string()), + }), + apply(resource, { kinds }) { const resourceKind = resource.kind.toLocaleLowerCase('en-US'); return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); }, - toQuery(kinds: string[]): EntitiesSearchFilter { + toQuery({ kinds }): EntitiesSearchFilter { return { key: 'kind', values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts index 11304c768c..f09e484c32 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts @@ -33,9 +33,11 @@ describe('isEntityOwner', () => { }, ], }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - true, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(true); }); it('returns false when entity is not owned by the given user', () => { @@ -52,9 +54,11 @@ describe('isEntityOwner', () => { }, ], }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - false, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(false); }); it('returns false when entity does not have an owner', () => { @@ -65,15 +69,21 @@ describe('isEntityOwner', () => { name: 'some-component', }, }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - false, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(false); }); }); describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(isEntityOwner.toQuery(['user:default/spiderman'])).toEqual({ + expect( + isEntityOwner.toQuery({ + claims: ['user:default/spiderman'], + }), + ).toEqual({ key: 'relations.ownedBy', values: ['user:default/spiderman'], }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index c0cc7c9911..27431f09a7 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; @@ -29,8 +29,10 @@ export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.tuple([z.array(z.string().describe('List of owners'))]), - apply: (resource: Entity, claims: string[]) => { + schema: z.object({ + claims: z.array(z.string()), + }), + apply: (resource, { claims }) => { if (!resource.relations) { return false; } @@ -39,7 +41,7 @@ export const isEntityOwner = createCatalogPermissionRule({ .filter(relation => relation.type === RELATION_OWNED_BY) .some(relation => claims.includes(relation.targetRef)); }, - toQuery: (claims: string[]) => ({ + toQuery: ({ claims }) => ({ key: 'relations.ownedBy', values: claims, }), diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 36b351595d..37544a14f8 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -29,8 +29,9 @@ import { EntitiesSearchFilter } from '../../catalog/types'; * * @alpha */ -export type CatalogPermissionRule = - PermissionRule; +export type CatalogPermissionRule< + TParams extends Record = Record, +> = PermissionRule; /** * Helper function for creating correctly-typed diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts index b15d1df4bb..66764dc89f 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts @@ -65,7 +65,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); const catalog = createCatalog(isEntityKind); @@ -117,7 +117,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); fakeCatalog.entities.mockResolvedValue({ entities: [] }); @@ -136,7 +136,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); fakeCatalog.entities.mockResolvedValue({ @@ -272,7 +272,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); const catalog = createCatalog(isEntityKind); diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 163e5e0747..088e6c71e2 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -696,7 +696,9 @@ describe('NextRouter permissioning', () => { name: 'FAKE_RULE', description: 'fake rule', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.tuple([]), + schema: z.object({ + foo: z.string(), + }), apply: () => true, toQuery: () => ({ key: '', values: [] }), }); @@ -760,7 +762,9 @@ describe('NextRouter permissioning', () => { conditions: { rule: 'FAKE_RULE', resourceType: 'catalog-entity', - params: ['user:default/spiderman'], + params: { + foo: 'user:default/spiderman', + }, }, }, ], diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index 6c05a825ab..8cbeeaebd9 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -39,8 +39,12 @@ describe('PermissionIntegrationClient', () => { const mockConditions: PermissionCriteria = { not: { allOf: [ - { rule: 'RULE_1', resourceType: 'test-resource', params: [] }, - { rule: 'RULE_2', resourceType: 'test-resource', params: ['abc'] }, + { rule: 'RULE_1', resourceType: 'test-resource', params: {} }, + { + rule: 'RULE_2', + resourceType: 'test-resource', + params: { foo: 'abc' }, + }, ], }, }; @@ -280,8 +284,10 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([z.enum(['yes', 'no'])]), - apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', + schema: z.object({ + input: z.enum(['yes', 'no']), + }), + apply: (_resource, { input }) => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, @@ -290,8 +296,11 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([z.enum(['yes', 'no'])]), - apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', + + schema: z.object({ + input: z.enum(['yes', 'no']), + }), + apply: (_resource, { input }) => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, @@ -347,7 +356,9 @@ describe('PermissionIntegrationClient', () => { conditions: { rule: 'RULE_1', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, }, ]), @@ -368,13 +379,17 @@ describe('PermissionIntegrationClient', () => { { rule: 'RULE_1', resourceType: 'test-resource', - params: ['yes'], + params: { + input: 'yes', + }, }, { not: { rule: 'RULE_2', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, }, ], @@ -385,12 +400,16 @@ describe('PermissionIntegrationClient', () => { { rule: 'RULE_1', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, { rule: 'RULE_2', resourceType: 'test-resource', - params: ['yes'], + params: { + input: 'yes', + }, }, ], }, diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index 9b2bdcb9ff..bcc6ddd6f6 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -227,7 +227,7 @@ describe('PermissionClient', () => { conditions: { resourceType: 'test-resource', rule: 'FOO', - params: ['bar'], + params: { foo: 'bar' }, }, }), ); @@ -275,7 +275,7 @@ describe('PermissionClient', () => { conditions: { rule: 'FOO', resourceType: 'test-resource', - params: ['bar'], + params: { foo: 'bar' }, }, }), ); diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 9a7bb0bba4..7b429dc46f 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -41,7 +41,7 @@ const permissionCriteriaSchema: z.ZodSchema< .object({ rule: z.string(), resourceType: z.string(), - params: z.array(z.unknown()), + params: z.record(z.unknown()), }) .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() })) .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() })) diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index f29a639d18..70b9e902b8 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -101,7 +101,7 @@ export type PolicyDecision = */ export type PermissionCondition< TResourceType extends string = string, - TParams extends unknown[] = unknown[], + TParams extends Record = Record, > = { resourceType: TResourceType; rule: string; diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index ed0ab49b5c..a989206fdd 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -31,25 +31,28 @@ const testIntegration = () => name: 'testRule1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([z.string(), z.number()]), - apply: jest.fn( - (_resource: any, _firstParam: string, _secondParam: number) => true, - ), - toQuery: jest.fn((firstParam: string, secondParam: number) => ({ + schema: z.object({ + foo: z.string(), + bar: z.number(), + }), + apply: (_resource: any, _params) => true, + toQuery: params => ({ query: 'testRule1', - params: [firstParam, secondParam], - })), + params, + }), }), testRule2: createPermissionRule({ name: 'testRule2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([z.object({})]), - apply: jest.fn((_resource: any, _firstParam: object) => false), - toQuery: jest.fn((firstParam: object) => ({ + schema: z.object({ + foo: z.object({}), + }), + apply: (_resource: any) => false, + toQuery: params => ({ query: 'testRule2', - params: [firstParam], - })), + params, + }), }), }, }); @@ -59,16 +62,26 @@ describe('createConditionExports', () => { it('creates condition factories for the supplied rules', () => { const { conditions } = testIntegration(); - expect(conditions.testRule1('a', 1)).toEqual({ + expect( + conditions.testRule1({ + foo: 'a', + bar: 1, + }), + ).toEqual({ rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }); - expect(conditions.testRule2({ baz: 'quux' })).toEqual({ + expect(conditions.testRule2({ foo: { baz: 'quux' } })).toEqual({ rule: 'testRule2', resourceType: 'test-resource', - params: [{ baz: 'quux' }], + params: { + foo: { baz: 'quux' }, + }, }); }); }); @@ -88,7 +101,10 @@ describe('createConditionExports', () => { { rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, ], }), @@ -101,7 +117,10 @@ describe('createConditionExports', () => { { rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, ], }, diff --git a/plugins/permission-node/src/integration/createConditionExports.ts b/plugins/permission-node/src/integration/createConditionExports.ts index ba34c07fed..3921c2dfe3 100644 --- a/plugins/permission-node/src/integration/createConditionExports.ts +++ b/plugins/permission-node/src/integration/createConditionExports.ts @@ -36,7 +36,7 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? (params: TParams) => PermissionCondition : never; /** diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 433d37b3f6..191688f0dd 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -23,9 +23,11 @@ describe('createConditionFactory', () => { name: 'test-rule', description: 'test-description', resourceType: 'test-resource', - schema: z.tuple([]), - apply: jest.fn(), - toQuery: jest.fn(), + schema: z.object({ + foo: z.string(), + }), + apply: (_resource, _params) => true, + toQuery: _params => ({}), }); it('returns a function', () => { @@ -35,10 +37,16 @@ describe('createConditionFactory', () => { describe('return value', () => { it('constructs a condition with the rule name and supplied params', () => { const conditionFactory = createConditionFactory(testRule); - expect(conditionFactory('a', 'b', 1, 2)).toEqual({ + expect( + conditionFactory({ + foo: 'bar', + }), + ).toEqual({ rule: 'test-rule', resourceType: 'test-resource', - params: ['a', 'b', 1, 2], + params: { + foo: 'bar', + }, }); }); }); diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 15a5b4fd08..5b499ab512 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -34,10 +34,10 @@ import { PermissionRule } from '../types'; * @public */ export const createConditionFactory = - ( + >( rule: PermissionRule, ) => - (...params: TParams): PermissionCondition => ({ + (params: TParams): PermissionCondition => ({ rule: rule.name, resourceType: rule.resourceType, params, diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index e7e1e89d06..5021b7766d 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -27,22 +27,22 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([z.string(), z.number()]), + schema: z.object({ + foo: z.string(), + bar: z.number(), + }), apply: jest.fn(), - toQuery: jest.fn( - (firstParam: string, secondParam: number) => - `test-rule-1:${firstParam}/${secondParam}`, - ), + toQuery: jest.fn(({ foo, bar }) => `test-rule-1:${foo}/${bar}`), }), createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([z.object({})]), + schema: z.object({ + foo: z.object({}), + }), apply: jest.fn(), - toQuery: jest.fn( - (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`, - ), + toQuery: jest.fn(({ foo }) => `test-rule-2:${JSON.stringify(foo)}`), }), ]); @@ -55,7 +55,10 @@ describe('createConditionTransformer', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['abc', 123], + params: { + foo: 'abc', + bar: 123, + }, }, expectedResult: 'test-rule-1:abc/123', }, @@ -63,7 +66,9 @@ describe('createConditionTransformer', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ foo: 0 }], + params: { + foo: { foo: 0 }, + }, }, expectedResult: 'test-rule-2:{"foo":0}', }, @@ -73,9 +78,18 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: {}, + }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { @@ -88,9 +102,18 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: {}, + }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { @@ -102,7 +125,9 @@ describe('createConditionTransformer', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, expectedResult: { @@ -117,12 +142,17 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, ], }, @@ -132,12 +162,19 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['b', 2], + params: { + foo: 'b', + bar: 2, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ c: 3 }], + params: { + foo: { + c: 3, + }, + }, }, ], }, @@ -165,12 +202,19 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ b: 2 }], + params: { + foo: { + b: 2, + }, + }, }, ], }, @@ -180,13 +224,20 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['c', 3], + params: { + foo: 'c', + bar: 3, + }, }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ d: 4 }], + params: { + foo: { + d: 4, + }, + }, }, }, ], diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index b40063ec13..afbd4a9316 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -53,7 +53,7 @@ const mapConditions = ( throw new InputError(`Parameters to rule are invalid`, result.error); } - return rule.toQuery(...criteria.params); + return rule.toQuery(criteria.params); }; /** diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 7d80459ba3..9c6b345faf 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -40,21 +40,23 @@ const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([ - z.string().describe('firstParam'), - z.number().describe('secondParam'), - ]), - apply: (_resource: any, _firstParam: string, _secondParam: number) => true, - toQuery: (_firstParam: string, _secondParam: number) => ({}), + schema: z.object({ + foo: z.string(), + bar: z.number().describe('bar'), + }), + apply: (_resource: any, _params) => true, + toQuery: _params => ({}), }); const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([z.object({}).describe('firstParam')]), - apply: (_resource: any, _firstParam: object) => false, - toQuery: (_firstParam: object) => ({}), + schema: z.object({ + foo: z.object({}).describe('foo'), + }), + apply: (_resource: any, _foo) => false, + toQuery: _foo => ({}), }); describe('createPermissionIntegrationRouter', () => { @@ -85,23 +87,35 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['abc', 123], + params: { + foo: 'abc', + bar: 123, + }, }, { anyOf: [ { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { foo: {} }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -111,12 +125,17 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, ], }, @@ -126,12 +145,17 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['b', 2], + params: { + foo: 'b', + bar: 2, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ c: 3 }], + params: { + foo: { c: 3 }, + }, }, ], }, @@ -167,16 +191,25 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ foo: 0 }], + params: { + foo: { foo: 0 }, + }, }, { allOf: [ { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { foo: {} }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { @@ -186,12 +219,17 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ b: 2 }], + params: { + foo: { b: 2 }, + }, }, ], }, @@ -201,13 +239,18 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['c', 3], + params: { + foo: 'c', + bar: 3, + }, }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ d: 4 }], + params: { + foo: { d: 4 }, + }, }, }, ], @@ -253,7 +296,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -263,7 +309,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -274,7 +322,10 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, }, }, @@ -286,7 +337,9 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, }, @@ -299,12 +352,17 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, ], }, @@ -348,7 +406,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-incorrect-resource-1', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -358,7 +418,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -368,7 +430,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-incorrect-resource-2', - params: [{}], + params: { + foo: {}, + }, }, }, ], @@ -396,7 +460,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: {}, }, }, ], @@ -433,7 +497,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -443,7 +510,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -453,7 +523,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, }, ], @@ -532,21 +605,20 @@ describe('createPermissionIntegrationRouter', () => { resourceType: testRule1.resourceType, schema: { $schema: 'http://json-schema.org/draft-07/schema#', - items: [ - { - description: 'firstParam', + additionalProperties: false, + properties: { + foo: { type: 'string', }, - { - description: 'secondParam', + bar: { + description: 'bar', type: 'number', }, - ], - maxItems: 2, - minItems: 2, - type: 'array', + }, + required: ['foo', 'bar'], + type: 'object', }, - parameters: { count: 2 }, + parameters: { count: 1 }, }, { name: testRule2.name, @@ -554,17 +626,17 @@ describe('createPermissionIntegrationRouter', () => { resourceType: testRule2.resourceType, schema: { $schema: 'http://json-schema.org/draft-07/schema#', - items: [ - { + additionalProperties: false, + properties: { + foo: { additionalProperties: false, - description: 'firstParam', + description: 'foo', properties: {}, type: 'object', }, - ], - maxItems: 1, - minItems: 1, - type: 'array', + }, + required: ['foo'], + type: 'object', }, parameters: { count: 1 }, }, diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 28268b7f98..c5843f6a84 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -46,7 +46,7 @@ const permissionCriteriaSchema: z.ZodSchema< z.object({ rule: z.string(), resourceType: z.string(), - params: z.array(z.unknown()), + params: z.record(z.unknown()), }), ]), ); @@ -132,7 +132,7 @@ const applyConditions = ( throw new InputError(`Parameters to rule are invalid`, result.error); } - return rule.apply(resource, ...criteria.params); + return rule.apply(resource, criteria.params); }; /** diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index 6673fe76de..ef84a5a26f 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -25,7 +25,7 @@ export const createPermissionRule = < TResource, TQuery, TResourceType extends string, - TParams extends unknown[], + TParams extends Record, >( rule: PermissionRule, ) => rule; @@ -40,7 +40,7 @@ export const createPermissionRule = < */ export const makeCreatePermissionRule = () => - ( + >( rule: PermissionRule, ) => createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/util.test.ts b/plugins/permission-node/src/integration/util.test.ts index 1572964a7d..924ab30757 100644 --- a/plugins/permission-node/src/integration/util.test.ts +++ b/plugins/permission-node/src/integration/util.test.ts @@ -31,7 +31,7 @@ describe('permission integration utils', () => { name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.tuple([]), + schema: z.object({}), apply: jest.fn(), toQuery: jest.fn(), }); @@ -40,7 +40,7 @@ describe('permission integration utils', () => { name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.tuple([]), + schema: z.object({}), apply: jest.fn(), toQuery: jest.fn(), }); diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 7beb5bb92d..02ebe07ac4 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -37,7 +37,7 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends unknown[] = unknown[], + TParams extends Record = Record, > = { name: string; description: string; @@ -46,19 +46,23 @@ export type PermissionRule< /** * A ZodSchema that documents the parameters that this rule accepts. */ - schema: z.ZodSchema; + schema: z.ZodObject<{ + [P in keyof TParams]-?: TParams[P] extends undefined + ? z.ZodOptionalType> + : z.ZodType; + }>; /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply(resource: TResource, ...params: TParams): boolean; + apply(resource: TResource, params: TParams): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery(...params: TParams): PermissionCriteria; + toQuery(params: TParams): PermissionCriteria; }; diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts index 179b0d7f9b..3d761bfd35 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts @@ -67,11 +67,10 @@ describe('DefaultPlaylistPermissionPolicy', () => { resourceType: PLAYLIST_LIST_RESOURCE_TYPE, conditions: { anyOf: [ - playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), - playlistConditions.isPublic(), + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), + playlistConditions.isPublic({}), ], }, }); @@ -89,11 +88,10 @@ describe('DefaultPlaylistPermissionPolicy', () => { resourceType: PLAYLIST_LIST_RESOURCE_TYPE, conditions: { anyOf: [ - playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), - playlistConditions.isPublic(), + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), + playlistConditions.isPublic({}), ], }, }); @@ -109,10 +107,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { result: AuthorizeResult.CONDITIONAL, pluginId: 'playlist', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - conditions: playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + conditions: playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), }); }); @@ -126,10 +123,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { result: AuthorizeResult.CONDITIONAL, pluginId: 'playlist', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - conditions: playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + conditions: playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), }); }); }); diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts index 86d103e789..317f5da9c8 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts @@ -68,8 +68,10 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { ) { return createPlaylistConditionalDecision(request.permission, { anyOf: [ - playlistConditions.isOwner(user?.identity.ownershipEntityRefs ?? []), - playlistConditions.isPublic(), + playlistConditions.isOwner({ + owners: user?.identity.ownershipEntityRefs ?? [], + }), + playlistConditions.isPublic({}), ], }); } @@ -81,7 +83,9 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { ) { return createPlaylistConditionalDecision( request.permission, - playlistConditions.isOwner(user?.identity.ownershipEntityRefs ?? []), + playlistConditions.isOwner({ + owners: user?.identity.ownershipEntityRefs ?? [], + }), ); } diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index de2d3df5e4..897d2e19eb 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -29,16 +29,19 @@ const createPlaylistPermissionRule = makeCreatePermissionRule< typeof PLAYLIST_LIST_RESOURCE_TYPE >(); -const isOwner = createPlaylistPermissionRule({ +const isOwner = createPlaylistPermissionRule<{ + owners: string[]; +}>({ name: 'IS_OWNER', description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - schema: z.tuple([z.array(z.string().describe('List of owners'))]), - apply: (list: PlaylistMetadata, userOwnershipRefs: string[]) => - userOwnershipRefs.includes(list.owner), - toQuery: (userOwnershipRefs: string[]) => ({ + schema: z.object({ + owners: z.array(z.string().describe('List of owner entity refs')), + }), + apply: (list: PlaylistMetadata, { owners }) => owners.includes(list.owner), + toQuery: ({ owners }) => ({ key: 'owner', - values: userOwnershipRefs, + values: owners, }), }); @@ -46,7 +49,7 @@ const isPublic = createPlaylistPermissionRule({ name: 'IS_PUBLIC', description: 'Should allow only if the playlist is public', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - schema: z.tuple([]), + schema: z.object({}), apply: (list: PlaylistMetadata) => list.public, toQuery: () => ({ key: 'public', values: [true] }), }); From 1e621ba7c859ccd730802216a7f36b535c1fe204 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Mon, 3 Oct 2022 12:45:48 +0100 Subject: [PATCH 040/160] Updated changeset Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 95899bf064..44bfdb6297 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -1,8 +1,31 @@ --- '@backstage/plugin-catalog-backend': minor '@backstage/plugin-permission-backend': minor +'@backstage/plugin-permission-common': minor '@backstage/plugin-permission-node': minor '@backstage/plugin-playlist-backend': minor --- -Permission rules now require a schema (ZodSchema) that details the parameters a rule expects. This is to validate the parameters given to a rule and to provide more details of a rule via the metadata endpoint +**BREAKING**: When defining permission rules, they must now also include a ZodSchema that details the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + +To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be seprate arguments, like so... + +```ts +createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, +}); +``` + +The API has now changed to expect the parameters as a single object + +```ts +createPermissionRule({ + schema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, +}); +``` From 708ff4761a26d76466f5ce6a91f7d163f6c571a9 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Mon, 3 Oct 2022 13:05:16 +0100 Subject: [PATCH 041/160] Explicitly type optional parameters so the definitions are accurate Signed-off-by: Harry Hogg --- .../src/permissions/rules/createPropertyRule.ts | 5 ++++- .../catalog-backend/src/permissions/rules/hasAnnotation.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index da487c112c..9ea67ec0b2 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -20,7 +20,10 @@ import { createCatalogPermissionRule } from './util'; import { z } from 'zod'; export const createPropertyRule = (propertyType: 'metadata' | 'spec') => - createCatalogPermissionRule({ + createCatalogPermissionRule<{ + key: string; + value?: string; + }>({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 9651e9d925..28c99e39a1 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -26,7 +26,10 @@ import { createCatalogPermissionRule } from './util'; * * @alpha */ -export const hasAnnotation = createCatalogPermissionRule({ +export const hasAnnotation = createCatalogPermissionRule<{ + annotation: string; + value?: string; +}>({ name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', From 2c37e5efe849ab9cd01e573978b97844c77010a3 Mon Sep 17 00:00:00 2001 From: Harrison Hogg <7130591+HHogg@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:34:29 +0100 Subject: [PATCH 042/160] Update .changeset/kind-bees-suffer.md Co-authored-by: MT Lewis Signed-off-by: Harrison Hogg <7130591+HHogg@users.noreply.github.com> Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 44bfdb6297..8e6e03d1bd 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -6,7 +6,7 @@ '@backstage/plugin-playlist-backend': minor --- -**BREAKING**: When defining permission rules, they must now also include a ZodSchema that details the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. +**BREAKING**: When defining permission rules, it's now necessary to provide a ZodSchema that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be seprate arguments, like so... From 7a3a0e3422760d53d49c88e0e7fa3c59bb5dcfdc Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 08:59:05 +0100 Subject: [PATCH 043/160] Fixed typo in changeset Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 8e6e03d1bd..372d7c1d86 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -8,7 +8,7 @@ **BREAKING**: When defining permission rules, it's now necessary to provide a ZodSchema that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. -To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be seprate arguments, like so... +To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... ```ts createPermissionRule({ From 1d4b847c98ddecf4421c8a2f3827db45dcc8463a Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 09:11:10 +0100 Subject: [PATCH 044/160] Explicitly use the schema to infer the types for the permission rule Signed-off-by: Harry Hogg --- .../integration/createPermissionIntegrationRouter.ts | 10 +--------- plugins/permission-node/src/types.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index c5843f6a84..9bcc6c57eb 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -28,7 +28,7 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; -import { PermissionRule } from '../types'; +import { NoInfer, PermissionRule } from '../types'; import { createGetRule, isAndCriteria, @@ -135,14 +135,6 @@ const applyConditions = ( return rule.apply(resource, criteria.params); }; -/** - * Prevent use of type parameter from contributing to type inference. - * - * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 - * @ignore - */ -type NoInfer = T extends infer S ? S : never; - /** * Create an express Router which provides an authorization route to allow * integration between the permission backend and other Backstage backend diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 02ebe07ac4..8725316de0 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -17,6 +17,14 @@ import type { PermissionCriteria } from '@backstage/plugin-permission-common'; import { z } from 'zod'; +/** + * Prevent use of type parameter from contributing to type inference. + * + * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 + * @ignore + */ +export type NoInfer = T extends infer S ? S : never; + /** * A conditional rule that can be provided in an * {@link @backstage/permission-common#AuthorizeDecision} response to an authorization request. @@ -57,12 +65,12 @@ export type PermissionRule< * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply(resource: TResource, params: TParams): boolean; + apply(resource: TResource, params: NoInfer): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery(params: TParams): PermissionCriteria; + toQuery(params: NoInfer): PermissionCriteria; }; From 755361681cea12a511843c2c6e1c3a00234c1e65 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 09:12:05 +0100 Subject: [PATCH 045/160] Add explanation comment around the schema type and whay we need to remove the optional def for the schema Signed-off-by: Harry Hogg --- plugins/permission-node/src/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 8725316de0..521c8ac3ad 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -55,6 +55,10 @@ export type PermissionRule< * A ZodSchema that documents the parameters that this rule accepts. */ schema: z.ZodObject<{ + // Parameters can be optional, however we we want to make sure that the + // parameters are always present in the schema, even if they are undefined. + // We remove the optional flag from the schema, and then add it back in + // with an optional zod type. [P in keyof TParams]-?: TParams[P] extends undefined ? z.ZodOptionalType> : z.ZodType; From 42fa9cdcdbd2d5569de517fb0fb5b00a4045e6d8 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 09:15:33 +0100 Subject: [PATCH 046/160] Removed the parameters count from the permissions metadata endpoint Signed-off-by: Harry Hogg --- .../src/integration/createPermissionIntegrationRouter.test.ts | 2 -- .../src/integration/createPermissionIntegrationRouter.ts | 3 --- 2 files changed, 5 deletions(-) diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 9c6b345faf..99e0da8287 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -618,7 +618,6 @@ describe('createPermissionIntegrationRouter', () => { required: ['foo', 'bar'], type: 'object', }, - parameters: { count: 1 }, }, { name: testRule2.name, @@ -638,7 +637,6 @@ describe('createPermissionIntegrationRouter', () => { required: ['foo'], type: 'object', }, - parameters: { count: 1 }, }, ], }); diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 9bcc6c57eb..7f355467c3 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -195,9 +195,6 @@ export const createPermissionIntegrationRouter = < description: rule.description, resourceType: rule.resourceType, schema: zodToJsonSchema(rule.schema), - parameters: { - count: rule.toQuery.length, - }, })); return res.json({ permissions, rules: serializableRules }); From 445c5f41a5464f8a7a6ab8c90c6ed1e34131b539 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 09:34:16 +0100 Subject: [PATCH 047/160] Reworded and added missing parameter descriptions Signed-off-by: Harry Hogg --- .../src/permissions/rules/createPropertyRule.ts | 6 ++++-- .../catalog-backend/src/permissions/rules/hasAnnotation.ts | 4 ++-- plugins/catalog-backend/src/permissions/rules/hasLabel.ts | 2 +- .../catalog-backend/src/permissions/rules/isEntityKind.ts | 4 +++- .../catalog-backend/src/permissions/rules/isEntityOwner.ts | 6 +++++- plugins/permission-node/src/types.ts | 2 +- plugins/playlist-backend/src/permissions/rules.ts | 2 +- 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 9ea67ec0b2..5662b27d48 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -28,11 +28,13 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') => description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, schema: z.object({ - key: z.string().describe(`The key of the ${propertyType} to match on`), + key: z + .string() + .describe(`Property within the entities ${propertyType} to match on`), value: z .string() .optional() - .describe(`Optional value of the ${propertyType} to match on`), + .describe(`Value of the given property to match on`), }), apply: (resource, { key, value }) => { const foundValue = get(resource[propertyType], key); diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 28c99e39a1..0d80977c09 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -35,11 +35,11 @@ export const hasAnnotation = createCatalogPermissionRule<{ 'Allow entities which are annotated with the specified annotation', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, schema: z.object({ - annotation: z.string().describe('The name of the annotation to match on'), + annotation: z.string().describe('Name of the annotation to match on'), value: z .string() .optional() - .describe('Optional value of the annotation to match on'), + .describe('Value of the annotation to match on'), }), apply: (resource, { annotation, value }) => !!resource.metadata.annotations?.hasOwnProperty(annotation) && diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index fbad7e42d0..6b7b9d9da0 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -28,7 +28,7 @@ export const hasLabel = createCatalogPermissionRule({ description: 'Allow entities which have the specified label metadata.', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, schema: z.object({ - label: z.string().describe('Name of the label'), + label: z.string().describe('Name of the label to match one'), }), apply: (resource, { label }) => !!resource.metadata.labels?.hasOwnProperty(label), diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 8c16f4c5a1..d64b357d9f 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -28,7 +28,9 @@ export const isEntityKind = createCatalogPermissionRule({ description: 'Allow entities with the specified kind', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, schema: z.object({ - kinds: z.array(z.string()), + kinds: z + .array(z.string()) + .describe('List of kinds to match at least one of'), }), apply(resource, { kinds }) { const resourceKind = resource.kind.toLocaleLowerCase('en-US'); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 27431f09a7..824e5e9ca6 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -30,7 +30,11 @@ export const isEntityOwner = createCatalogPermissionRule({ description: 'Allow entities owned by the current user', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, schema: z.object({ - claims: z.array(z.string()), + claims: z + .array(z.string()) + .describe( + `List of claims to match at least one on within ${RELATION_OWNED_BY}`, + ), }), apply: (resource, { claims }) => { if (!resource.relations) { diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 521c8ac3ad..e69c1f3d78 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -61,7 +61,7 @@ export type PermissionRule< // with an optional zod type. [P in keyof TParams]-?: TParams[P] extends undefined ? z.ZodOptionalType> - : z.ZodType; + : z.ZodType; }>; /** diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index 897d2e19eb..79214b050d 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -36,7 +36,7 @@ const isOwner = createPlaylistPermissionRule<{ description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, schema: z.object({ - owners: z.array(z.string().describe('List of owner entity refs')), + owners: z.array(z.string()).describe('List of owner entity refs'), }), apply: (list: PlaylistMetadata, { owners }) => owners.includes(list.owner), toQuery: ({ owners }) => ({ From fbc636c4a505ed96b2c8da41aed43eb561f4479b Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 10:57:52 +0100 Subject: [PATCH 048/160] Use `z.input` to corrently type the input to correctly reflect optional fields Signed-off-by: Harry Hogg --- .../permissions/rules/createPropertyRule.ts | 5 +--- .../src/permissions/rules/hasAnnotation.ts | 5 +--- plugins/permission-node/src/types.ts | 23 ++++++++++--------- .../playlist-backend/src/permissions/rules.ts | 4 +--- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 5662b27d48..29ce67ad53 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -20,10 +20,7 @@ import { createCatalogPermissionRule } from './util'; import { z } from 'zod'; export const createPropertyRule = (propertyType: 'metadata' | 'spec') => - createCatalogPermissionRule<{ - key: string; - value?: string; - }>({ + createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 0d80977c09..c8450f3d08 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -26,10 +26,7 @@ import { createCatalogPermissionRule } from './util'; * * @alpha */ -export const hasAnnotation = createCatalogPermissionRule<{ - annotation: string; - value?: string; -}>({ +export const hasAnnotation = createCatalogPermissionRule({ name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index e69c1f3d78..6d39d7f922 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -46,6 +46,15 @@ export type PermissionRule< TQuery, TResourceType extends string, TParams extends Record = Record, + TSchema extends z.ZodType = z.ZodObject<{ + // Parameters can be optional, however we we want to make sure that the + // parameters are always present in the schema, even if they are undefined. + // We remove the optional flag from the schema, and then add it back in + // with an optional zod type. + [P in keyof TParams]-?: TParams[P] extends undefined + ? z.ZodOptionalType> + : z.ZodType; + }>, > = { name: string; description: string; @@ -54,27 +63,19 @@ export type PermissionRule< /** * A ZodSchema that documents the parameters that this rule accepts. */ - schema: z.ZodObject<{ - // Parameters can be optional, however we we want to make sure that the - // parameters are always present in the schema, even if they are undefined. - // We remove the optional flag from the schema, and then add it back in - // with an optional zod type. - [P in keyof TParams]-?: TParams[P] extends undefined - ? z.ZodOptionalType> - : z.ZodType; - }>; + schema: TSchema; /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply(resource: TResource, params: NoInfer): boolean; + apply(resource: TResource, params: NoInfer>): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery(params: NoInfer): PermissionCriteria; + toQuery(params: NoInfer>): PermissionCriteria; }; diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index 79214b050d..bebb7905b4 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -29,9 +29,7 @@ const createPlaylistPermissionRule = makeCreatePermissionRule< typeof PLAYLIST_LIST_RESOURCE_TYPE >(); -const isOwner = createPlaylistPermissionRule<{ - owners: string[]; -}>({ +const isOwner = createPlaylistPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, From 4eb0f6d23dd02fe7a8f62e547f1997a22c761c3a Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 11:39:46 +0100 Subject: [PATCH 049/160] Limited the permission rule parameters to JsonPrimatives and array of Signed-off-by: Harry Hogg --- .../src/permissions/rules/util.ts | 3 +- plugins/permission-common/package.json | 1 + .../permission-common/src/PermissionClient.ts | 2 +- plugins/permission-common/src/types/api.ts | 13 +++++- plugins/permission-common/src/types/index.ts | 2 + .../createConditionExports.test.ts | 6 +-- .../src/integration/createConditionFactory.ts | 10 ++++- .../createConditionTransformer.test.ts | 42 ++++++++----------- .../createPermissionIntegrationRouter.test.ts | 28 ++++++------- .../createPermissionIntegrationRouter.ts | 2 +- .../src/integration/createPermissionRule.ts | 5 ++- plugins/permission-node/src/types.ts | 7 +++- yarn.lock | 1 + 13 files changed, 70 insertions(+), 52 deletions(-) diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 37544a14f8..ea25e115d2 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { makeCreatePermissionRule, PermissionRule, @@ -30,7 +31,7 @@ import { EntitiesSearchFilter } from '../../catalog/types'; * @alpha */ export type CatalogPermissionRule< - TParams extends Record = Record, + TParams extends PermissionRuleParams = PermissionRuleParams, > = PermissionRule; /** diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index fcf1c47ea6..4c31ecc673 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -43,6 +43,7 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/types": "workspace:^", "cross-fetch": "^3.1.5", "uuid": "^8.0.0", "zod": "^3.11.6" diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 7b429dc46f..c5f56be159 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -41,7 +41,7 @@ const permissionCriteriaSchema: z.ZodSchema< .object({ rule: z.string(), resourceType: z.string(), - params: z.record(z.unknown()), + params: z.record(z.any()), }) .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() })) .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() })) diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index 70b9e902b8..ca915a65da 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { JsonPrimitive } from '@backstage/types'; import { ResourcePermission } from '.'; import { Permission } from './permission'; @@ -101,7 +102,7 @@ export type PolicyDecision = */ export type PermissionCondition< TResourceType extends string = string, - TParams extends Record = Record, + TParams extends PermissionRuleParams = PermissionRuleParams, > = { resourceType: TResourceType; rule: string; @@ -148,6 +149,16 @@ export type PermissionCriteria = | NotCriteria | TQuery; +/** + * A parameter to a permission rule. + */ +export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; + +/** + * Types that can be used as parameters to permission rules. + */ +export type PermissionRuleParams = Record; + /** * An individual request sent to the permission backend. * @public diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index f244a70b1a..5702f47c1c 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -33,6 +33,8 @@ export type { PolicyDecision, PermissionCondition, PermissionCriteria, + PermissionRuleParam, + PermissionRuleParams, AllOfCriteria, AnyOfCriteria, NotCriteria, diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index a989206fdd..59639f5119 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -46,7 +46,7 @@ const testIntegration = () => description: 'Test rule 2', resourceType: 'test-resource', schema: z.object({ - foo: z.object({}), + foo: z.string(), }), apply: (_resource: any) => false, toQuery: params => ({ @@ -76,11 +76,11 @@ describe('createConditionExports', () => { }, }); - expect(conditions.testRule2({ foo: { baz: 'quux' } })).toEqual({ + expect(conditions.testRule2({ foo: 'baz' })).toEqual({ rule: 'testRule2', resourceType: 'test-resource', params: { - foo: { baz: 'quux' }, + foo: 'baz', }, }); }); diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 5b499ab512..8edeb6230f 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { PermissionCondition } from '@backstage/plugin-permission-common'; +import { + PermissionCondition, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -34,7 +37,10 @@ import { PermissionRule } from '../types'; * @public */ export const createConditionFactory = - >( + < + TResourceType extends string, + TParams extends PermissionRuleParams = PermissionRuleParams, + >( rule: PermissionRule, ) => (params: TParams): PermissionCondition => ({ diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 5021b7766d..0544ff3ef8 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -39,10 +39,10 @@ const transformConditions = createConditionTransformer([ description: 'Test rule 2', resourceType: 'test-resource', schema: z.object({ - foo: z.object({}), + foo: z.string(), }), apply: jest.fn(), - toQuery: jest.fn(({ foo }) => `test-rule-2:${JSON.stringify(foo)}`), + toQuery: jest.fn(({ foo }) => `test-rule-2:${foo}`), }), ]); @@ -67,10 +67,10 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { foo: 0 }, + foo: '0', }, }, - expectedResult: 'test-rule-2:{"foo":0}', + expectedResult: 'test-rule-2:0', }, { conditions: { @@ -87,13 +87,13 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'b', }, }, ], }, expectedResult: { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, }, { @@ -111,13 +111,13 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'b', }, }, ], }, expectedResult: { - allOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + allOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, }, { @@ -126,12 +126,12 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'a', }, }, }, expectedResult: { - not: 'test-rule-2:{}', + not: 'test-rule-2:a', }, }, { @@ -151,7 +151,7 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'b', }, }, ], @@ -171,9 +171,7 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { - c: 3, - }, + foo: 'c', }, }, ], @@ -184,11 +182,11 @@ describe('createConditionTransformer', () => { expectedResult: { allOf: [ { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, { not: { - allOf: ['test-rule-1:b/2', 'test-rule-2:{"c":3}'], + allOf: ['test-rule-1:b/2', 'test-rule-2:c'], }, }, ], @@ -211,9 +209,7 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { - b: 2, - }, + foo: 'b', }, }, ], @@ -234,9 +230,7 @@ describe('createConditionTransformer', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { - d: 4, - }, + foo: 'd', }, }, }, @@ -248,11 +242,11 @@ describe('createConditionTransformer', () => { expectedResult: { allOf: [ { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{"b":2}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, { not: { - allOf: ['test-rule-1:c/3', { not: 'test-rule-2:{"d":4}' }], + allOf: ['test-rule-1:c/3', { not: 'test-rule-2:d' }], }, }, ], diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 99e0da8287..adfa57b863 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -53,7 +53,7 @@ const testRule2 = createPermissionRule({ description: 'Test rule 2', resourceType: 'test-resource', schema: z.object({ - foo: z.object({}).describe('foo'), + foo: z.string().describe('foo'), }), apply: (_resource: any, _foo) => false, toQuery: _foo => ({}), @@ -105,7 +105,7 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { foo: {} }, + params: { foo: 'b' }, }, ], }, @@ -114,7 +114,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'c', }, }, }, @@ -134,7 +134,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'b', }, }, ], @@ -154,7 +154,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { c: 3 }, + foo: 'c', }, }, ], @@ -192,7 +192,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { foo: 0 }, + foo: 'a', }, }, { @@ -208,7 +208,7 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { foo: {} }, + params: { foo: 'b' }, }, ], }, @@ -228,7 +228,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { b: 2 }, + foo: 'b', }, }, ], @@ -249,7 +249,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: { d: 4 }, + foo: 'd', }, }, }, @@ -310,7 +310,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'b', }, }, }, @@ -338,7 +338,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'c', }, }, }, @@ -361,7 +361,7 @@ describe('createPermissionIntegrationRouter', () => { rule: 'test-rule-2', resourceType: 'test-resource', params: { - foo: {}, + foo: 'd', }, }, ], @@ -628,10 +628,8 @@ describe('createPermissionIntegrationRouter', () => { additionalProperties: false, properties: { foo: { - additionalProperties: false, description: 'foo', - properties: {}, - type: 'object', + type: 'string', }, }, required: ['foo'], diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 7f355467c3..a01006c194 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -46,7 +46,7 @@ const permissionCriteriaSchema: z.ZodSchema< z.object({ rule: z.string(), resourceType: z.string(), - params: z.record(z.unknown()), + params: z.record(z.any()), }), ]), ); diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index ef84a5a26f..ea2e37a671 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -25,7 +26,7 @@ export const createPermissionRule = < TResource, TQuery, TResourceType extends string, - TParams extends Record, + TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, ) => rule; @@ -40,7 +41,7 @@ export const createPermissionRule = < */ export const makeCreatePermissionRule = () => - >( + ( rule: PermissionRule, ) => createPermissionRule(rule); diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 6d39d7f922..34ff52391a 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import type { PermissionCriteria } from '@backstage/plugin-permission-common'; +import type { + PermissionCriteria, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { z } from 'zod'; /** @@ -45,7 +48,7 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends Record = Record, + TParams extends PermissionRuleParams = PermissionRuleParams, TSchema extends z.ZodType = z.ZodObject<{ // Parameters can be optional, however we we want to make sure that the // parameters are always present in the schema, even if they are undefined. diff --git a/yarn.lock b/yarn.lock index bef0e188c6..168ca96586 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6334,6 +6334,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/types": "workspace:^" cross-fetch: ^3.1.5 msw: ^0.47.0 uuid: ^8.0.0 From 1ad9969a0f61eb8b973fe7e13a3871a61be87263 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 11:44:06 +0100 Subject: [PATCH 050/160] Updated changeset to include the new limitation of permission params types Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 372d7c1d86..528d06394e 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -29,3 +29,5 @@ createPermissionRule({ toQuery: ({ foo, bar }) => {}, }); ``` + +One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. From 5a8a8010eed7ca81fdd31c2bba5ae998ac2980d9 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 12:08:38 +0100 Subject: [PATCH 051/160] Removed plugin-permission-backend from changeset Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 528d06394e..41c9874590 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -1,6 +1,5 @@ --- '@backstage/plugin-catalog-backend': minor -'@backstage/plugin-permission-backend': minor '@backstage/plugin-permission-common': minor '@backstage/plugin-permission-node': minor '@backstage/plugin-playlist-backend': minor From 26e5513c32ecf334364beb7d369a570cb756158c Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 4 Oct 2022 12:50:08 +0100 Subject: [PATCH 052/160] Update API reports Signed-off-by: Harry Hogg --- plugins/catalog-backend/api-report.md | 66 ++++++++++++++----- plugins/permission-common/api-report.md | 9 ++- plugins/permission-common/src/types/api.ts | 4 ++ plugins/permission-node/api-report.md | 44 +++++++++---- .../createPermissionIntegrationRouter.ts | 3 +- .../permission-node/src/integration/util.ts | 8 +++ plugins/permission-node/src/types.ts | 37 ++++++----- plugins/playlist-backend/api-report.md | 9 ++- 8 files changed, 130 insertions(+), 50 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f18d396114..ea9b0b2277 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -40,6 +40,7 @@ import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { processingResult } from '@backstage/plugin-catalog-node'; @@ -177,37 +178,52 @@ export const catalogConditions: Conditions<{ Entity, EntitiesSearchFilter, 'catalog-entity', - [annotation: string, value?: string | undefined] + { + annotation: string; + value: string | undefined; + } >; hasLabel: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [label: string] + { + label: string; + } >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + key: string; + value: string | undefined; + } >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + key: string; + value: string | undefined; + } >; isEntityKind: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [kinds: string[]] + { + kinds: string[]; + } >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [claims: string[]] + { + claims: string[]; + } >; }>; @@ -221,8 +237,9 @@ export type CatalogEnvironment = { }; // @alpha -export type CatalogPermissionRule = - PermissionRule; +export type CatalogPermissionRule< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; // @alpha export const catalogPlugin: (options?: undefined) => BackendFeature; @@ -280,12 +297,14 @@ export class CodeOwnersProcessor implements CatalogProcessor { export const createCatalogConditionalDecision: ( permission: ResourcePermission<'catalog-entity'>, conditions: PermissionCriteria< - PermissionCondition<'catalog-entity', unknown[]> + PermissionCondition<'catalog-entity', PermissionRuleParams> >, ) => ConditionalPolicyDecision; // @alpha -export const createCatalogPermissionRule: ( +export const createCatalogPermissionRule: < + TParams extends PermissionRuleParams = PermissionRuleParams, +>( rule: PermissionRule, ) => PermissionRule; @@ -448,37 +467,52 @@ export const permissionRules: { Entity, EntitiesSearchFilter, 'catalog-entity', - [annotation: string, value?: string | undefined] + { + annotation: string; + value: string | undefined; + } >; hasLabel: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [label: string] + { + label: string; + } >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + key: string; + value: string | undefined; + } >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + key: string; + value: string | undefined; + } >; isEntityKind: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [kinds: string[]] + { + kinds: string[]; + } >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [claims: string[]] + { + claims: string[]; + } >; }; diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index c0c351d6cd..b51b82c907 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -4,6 +4,7 @@ ```ts import { Config } from '@backstage/config'; +import { JsonPrimitive } from '@backstage/types'; // @public export type AllOfCriteria = { @@ -172,7 +173,7 @@ export class PermissionClient implements PermissionEvaluator { // @public export type PermissionCondition< TResourceType extends string = string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { resourceType: TResourceType; rule: string; @@ -203,6 +204,12 @@ export type PermissionMessageBatch = { items: IdentifiedPermissionMessage[]; }; +// @public +export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; + +// @public +export type PermissionRuleParams = Record; + // @public export type PolicyDecision = | DefinitivePolicyDecision diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index ca915a65da..77d6206c07 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -151,11 +151,15 @@ export type PermissionCriteria = /** * A parameter to a permission rule. + * + * @public */ export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; /** * Types that can be used as parameters to permission rules. + * + * @public */ export type PermissionRuleParams = Record; diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 055325f5a7..5168eefebc 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -19,6 +19,7 @@ import { Permission } from '@backstage/plugin-permission-common'; import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PolicyDecision } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; @@ -54,7 +55,7 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? (params: TParams) => PermissionCondition : never; // @public @@ -75,7 +76,7 @@ export const createConditionExports: < TResource, TRules extends Record< string, - PermissionRule + PermissionRule >, >(options: { pluginId: string; @@ -86,7 +87,7 @@ export const createConditionExports: < createConditionalDecision: ( permission: ResourcePermission, conditions: PermissionCriteria< - PermissionCondition + PermissionCondition >, ) => ConditionalPolicyDecision; }; @@ -94,15 +95,15 @@ export const createConditionExports: < // @public export const createConditionFactory: < TResourceType extends string, - TParams extends any[], + TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, -) => (...params: TParams) => PermissionCondition; +) => (params: TParams) => PermissionCondition; // @public export const createConditionTransformer: < TQuery, - TRules extends PermissionRule[], + TRules extends PermissionRule[], >( permissionRules: [...TRules], ) => ConditionTransformer; @@ -114,7 +115,12 @@ export const createPermissionIntegrationRouter: < >(options: { resourceType: TResourceType; permissions?: Permission[] | undefined; - rules: PermissionRule, unknown[]>[]; + rules: PermissionRule< + TResource, + any, + NoInfer, + PermissionRuleParams + >[]; getResources: (resourceRefs: string[]) => Promise<(TResource | undefined)[]>; }) => express.Router; @@ -123,7 +129,7 @@ export const createPermissionRule: < TResource, TQuery, TResourceType extends string, - TParams extends unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, ) => PermissionRule; @@ -148,7 +154,7 @@ export const makeCreatePermissionRule: < TResource, TQuery, TResourceType extends string, ->() => ( +>() => ( rule: PermissionRule, ) => PermissionRule; @@ -166,16 +172,28 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { name: string; description: string; resourceType: TResourceType; - schema: z.ZodSchema; - apply(resource: TResource, ...params: TParams): boolean; - toQuery(...params: TParams): PermissionCriteria; + schema: PermissionRuleSchema; + apply( + resource: TResource, + params: NoInfer>>, + ): boolean; + toQuery( + params: NoInfer>>, + ): PermissionCriteria; }; +// @public +export type PermissionRuleSchema = z.ZodObject<{ + [P in keyof TParams]-?: TParams[P] extends undefined + ? z.ZodOptionalType> + : z.ZodType; +}>; + // @public export type PolicyQuery = { permission: Permission; diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index a01006c194..4f98597e6c 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -28,8 +28,9 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; -import { NoInfer, PermissionRule } from '../types'; +import { PermissionRule } from '../types'; import { + NoInfer, createGetRule, isAndCriteria, isNotCriteria, diff --git a/plugins/permission-node/src/integration/util.ts b/plugins/permission-node/src/integration/util.ts index 68b7fff2b8..e448068e17 100644 --- a/plugins/permission-node/src/integration/util.ts +++ b/plugins/permission-node/src/integration/util.ts @@ -22,6 +22,14 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; +/** + * Prevent use of type parameter from contributing to type inference. + * + * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 + * @ignore + */ +export type NoInfer = T extends infer S ? S : never; + /** * Utility function used to parse a PermissionCriteria * @param criteria - a PermissionCriteria diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 34ff52391a..b16c0cac32 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -19,14 +19,23 @@ import type { PermissionRuleParams, } from '@backstage/plugin-permission-common'; import { z } from 'zod'; +import { NoInfer } from './integration/util'; /** - * Prevent use of type parameter from contributing to type inference. + * A ZodSchema that reflects the structure of the parameters that are passed to + * into a {@link PermissionRule}. * - * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 - * @ignore + * @public */ -export type NoInfer = T extends infer S ? S : never; +export type PermissionRuleSchema = z.ZodObject<{ + // Parameters can be optional, however we we want to make sure that the + // parameters are always present in the schema, even if they are undefined. + // We remove the optional flag from the schema, and then add it back in + // with an optional zod type. + [P in keyof TParams]-?: TParams[P] extends undefined + ? z.ZodOptionalType> + : z.ZodType; +}>; /** * A conditional rule that can be provided in an @@ -49,15 +58,6 @@ export type PermissionRule< TQuery, TResourceType extends string, TParams extends PermissionRuleParams = PermissionRuleParams, - TSchema extends z.ZodType = z.ZodObject<{ - // Parameters can be optional, however we we want to make sure that the - // parameters are always present in the schema, even if they are undefined. - // We remove the optional flag from the schema, and then add it back in - // with an optional zod type. - [P in keyof TParams]-?: TParams[P] extends undefined - ? z.ZodOptionalType> - : z.ZodType; - }>, > = { name: string; description: string; @@ -66,19 +66,24 @@ export type PermissionRule< /** * A ZodSchema that documents the parameters that this rule accepts. */ - schema: TSchema; + schema: PermissionRuleSchema; /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply(resource: TResource, params: NoInfer>): boolean; + apply( + resource: TResource, + params: NoInfer>>, + ): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery(params: NoInfer>): PermissionCriteria; + toQuery( + params: NoInfer>>, + ): PermissionCriteria; }; diff --git a/plugins/playlist-backend/api-report.md b/plugins/playlist-backend/api-report.md index c7109efbfb..faae97a734 100644 --- a/plugins/playlist-backend/api-report.md +++ b/plugins/playlist-backend/api-report.md @@ -15,6 +15,7 @@ import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionPolicy } from '@backstage/plugin-permission-node'; import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PlaylistMetadata } from '@backstage/plugin-playlist-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -26,7 +27,7 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; export const createPlaylistConditionalDecision: ( permission: ResourcePermission<'playlist-list'>, conditions: PermissionCriteria< - PermissionCondition<'playlist-list', unknown[]> + PermissionCondition<'playlist-list', PermissionRuleParams> >, ) => ConditionalPolicyDecision; @@ -70,13 +71,15 @@ export const playlistConditions: Conditions<{ PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - [userOwnershipRefs: string[]] + { + owners: string[]; + } >; isPublic: PermissionRule< PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - [] + PermissionRuleParams >; }>; From c44cf412de245d6ccc39b32d4da37c6ac3fcf08b Mon Sep 17 00:00:00 2001 From: David Zemon Date: Tue, 4 Oct 2022 11:14:04 -0500 Subject: [PATCH 053/160] fix: [#13560] Pull commit hash for branch names by branch query Using the commits API was unstable and broke for branches with a '/'. Using the branches API works for any branch and is documented in the Atlassian API docs: https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp209 Signed-off-by: David Zemon --- .changeset/great-numbers-reply.md | 5 ++ .../reading/BitbucketServerUrlReader.test.ts | 46 ++++++++++++++++--- .../src/reading/BitbucketServerUrlReader.ts | 42 ++++++++--------- 3 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 .changeset/great-numbers-reply.md diff --git a/.changeset/great-numbers-reply.md b/.changeset/great-numbers-reply.md new file mode 100644 index 0000000000..b72b05debf --- /dev/null +++ b/.changeset/great-numbers-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Fix BitBucket server integration diff --git a/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts index f308b4d33d..15f1c0d4be 100644 --- a/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts @@ -83,11 +83,23 @@ describe('BitbucketServerUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/*', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches', (_, res, ctx) => res( ctx.status(200), - ctx.json({ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }), + ctx.json({ + size: 2, + values: [ + { + displayId: 'some-branch-that-should-be-ignored', + latestCommit: 'bogus hash', + }, + { + displayId: 'some-branch', + latestCommit: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st', + }, + ], + }), ), ), ); @@ -130,12 +142,22 @@ describe('BitbucketServerUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/*', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches', (_, res, ctx) => res( ctx.status(200), ctx.json({ - values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }], + size: 2, + values: [ + { + displayId: 'some-branch-that-should-be-ignored', + latestCommit: 'bogus hash', + }, + { + displayId: 'some-branch', + latestCommit: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st', + }, + ], }), ), ), @@ -179,11 +201,23 @@ describe('BitbucketServerUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/*', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches', (_, res, ctx) => res( ctx.status(200), - ctx.json({ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }), + ctx.json({ + size: 2, + values: [ + { + displayId: 'master-of-none', + latestCommit: 'bogus hash', + }, + { + displayId: 'master', + latestCommit: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st', + }, + ], + }), ), ), ); diff --git a/packages/backend-common/src/reading/BitbucketServerUrlReader.ts b/packages/backend-common/src/reading/BitbucketServerUrlReader.ts index e9a229a1ff..98f95b88cb 100644 --- a/packages/backend-common/src/reading/BitbucketServerUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketServerUrlReader.ts @@ -188,38 +188,36 @@ export class BitbucketServerUrlReader implements UrlReader { private async getLastCommitShortHash(url: string): Promise { const { name: repoName, owner: project, ref: branch } = parseGitUrl(url); - // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp224 - const commitApiUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits/${branch}`; + const branchListUrl = `${ + this.integration.config.apiBaseUrl + }/projects/${project}/repos/${repoName}/branches?filterText=${encodeURIComponent( + branch, + )}`; - const commitResponse = await fetch( - commitApiUrl, + const branchListResponse = await fetch( + branchListUrl, getBitbucketServerRequestOptions(this.integration.config), ); - if (!commitResponse.ok) { - const message = `Failed to retrieve commits from ${commitApiUrl}, ${commitResponse.status} ${commitResponse.statusText}`; - if (commitResponse.status === 404) { + if (!branchListResponse.ok) { + const message = `Failed to retrieve branch list from ${branchListUrl}, ${branchListResponse.status} ${branchListResponse.statusText}`; + if (branchListResponse.status === 404) { throw new NotFoundError(message); } throw new Error(message); } - const commits = await commitResponse.json(); + const branchMatches = await branchListResponse.json(); - // Handles case when a branch is provided in the URL - if (commits && commits.id) { - return commits.id.substring(0, 12); + if (branchMatches && branchMatches.size > 0) { + const exactBranchMatch = branchMatches.values.filter( + (branchDetails: { displayId: string }) => + branchDetails.displayId === branch, + )[0]; + return exactBranchMatch.latestCommit.substring(0, 12); } - // Handles case when no branch is provided in the URL - if ( - commits && - commits.values && - commits.values.length > 0 && - commits.values[0].id - ) { - return commits.values[0].id.substring(0, 12); - } - - throw new Error(`Failed to read response from ${commitApiUrl}`); + throw new Error( + `Failed to find branch "${branch}" in property "displayId" of response to ${branchListUrl}`, + ); } } From 5e8c2682fe307e2f8eb197c026d00f065376a841 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 5 Oct 2022 13:39:19 +0200 Subject: [PATCH 054/160] move types to catalog-common Signed-off-by: Kiss Miklos --- .../src/ingestion/LocationAnalyzer.ts | 5 +- .../catalog-backend/src/ingestion/index.ts | 5 -- .../catalog-backend/src/ingestion/types.ts | 78 ++-------------- plugins/catalog-common/package.json | 2 + plugins/catalog-common/src/index.ts | 1 + .../src/ingestion/LocationAnalyzer.ts | 88 +++++++++++++++++++ .../src/ingestion/RecursivePartial.test.ts | 31 +++++++ .../src/ingestion/RecursivePartial.ts | 27 ++++++ plugins/catalog-common/src/ingestion/index.ts | 21 +++++ plugins/catalog-import/package.json | 2 +- .../src/api/CatalogImportClient.ts | 2 +- yarn.lock | 4 +- 12 files changed, 182 insertions(+), 84 deletions(-) create mode 100644 plugins/catalog-common/src/ingestion/LocationAnalyzer.ts create mode 100644 plugins/catalog-common/src/ingestion/RecursivePartial.test.ts create mode 100644 plugins/catalog-common/src/ingestion/RecursivePartial.ts create mode 100644 plugins/catalog-common/src/ingestion/index.ts diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 3dc08a81c3..761a0f8c0a 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -18,12 +18,11 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { LocationAnalyzer, ScmLocationAnalyzer } from './types'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, - LocationAnalyzer, - ScmLocationAnalyzer, -} from './types'; +} from '@backstage/plugin-catalog-common'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 0b00809043..0fc71f6785 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -15,11 +15,6 @@ */ export type { - AnalyzeLocationEntityField, - AnalyzeLocationExistingEntity, - AnalyzeLocationGenerateEntity, - AnalyzeLocationRequest, - AnalyzeLocationResponse, LocationAnalyzer, ScmLocationAnalyzer, AnalyzeOptions, diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index c5237af6d3..c6a480a7f5 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Entity } from '@backstage/catalog-model'; -import { RecursivePartial } from '../util/RecursivePartial'; -import { LocationSpec } from '@backstage/plugin-catalog-node'; +import { + AnalyzeLocationResponse, + AnalyzeLocationRequest, + AnalyzeLocationExistingEntity, +} from '@backstage/plugin-catalog-common'; /** @public */ export type LocationAnalyzer = { @@ -31,75 +32,6 @@ export type LocationAnalyzer = { ): Promise; }; -/** @public */ -export type AnalyzeLocationRequest = { - location: LocationSpec; - catalogFilename?: string; -}; - -/** @public */ -export type AnalyzeLocationResponse = { - existingEntityFiles: AnalyzeLocationExistingEntity[]; - generateEntities: AnalyzeLocationGenerateEntity[]; -}; - -/** - * If the folder pointed to already contained catalog info yaml files, they are - * read and emitted like this so that the frontend can inform the user that it - * located them and can make sure to register them as well if they weren't - * already - * @public - */ -export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; -}; - -/** - * This is some form of representation of what the analyzer could deduce. - * We should probably have a chat about how this can best be conveyed to - * the frontend. It'll probably contain a (possibly incomplete) entity, plus - * enough info for the frontend to know what form data to show to the user - * for overriding/completing the info. - * @public - */ -export type AnalyzeLocationGenerateEntity = { - // Some form of partial representation of the entity - entity: RecursivePartial; - // Lists the suggestions that the user may want to override - fields: AnalyzeLocationEntityField[]; -}; - -// This is where I get really vague. Something like this perhaps? Or it could be -// something like a json-schema that contains enough info for the frontend to -// be able to present a form and explanations -/** @public */ -export type AnalyzeLocationEntityField = { - /** - * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the - * entity again if the user wants to change it - */ - field: string; - - /** The outcome of the analysis for this particular field */ - state: - | 'analysisSuggestedValue' - | 'analysisSuggestedNoValue' - | 'needsUserInput'; - - // If the analysis did suggest a value, this is where it would be. Not sure if we want - // to limit this to strings or if we want it to be any JsonValue - value: string | null; - /** - * A text to show to the user to inform about the choices made. Like, it could say - * "Found a CODEOWNERS file that covers this target, so we suggest leaving this - * field empty; which would currently make it owned by X" where X is taken from the - * codeowners file. - */ - description: string; -}; - /** @public */ export type AnalyzeOptions = { url: string; diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index ecb53d35de..96e9f31b43 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -33,6 +33,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^" }, diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index 614948e56c..a37fd122f9 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -35,3 +35,4 @@ export { export type { CatalogEntityPermission } from './permissions'; export * from './search'; +export * from './ingestion'; diff --git a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts new file mode 100644 index 0000000000..8d570894cf --- /dev/null +++ b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts @@ -0,0 +1,88 @@ +/* + * 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 { LocationSpec } from '@backstage/plugin-catalog-node'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from './RecursivePartial'; + +/** @public */ +export type AnalyzeLocationRequest = { + location: LocationSpec; + catalogFilename?: string; +}; + +/** @public */ +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + +/** + * If the folder pointed to already contained catalog info yaml files, they are + * read and emitted like this so that the frontend can inform the user that it + * located them and can make sure to register them as well if they weren't + * already + * @public + */ +export type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +/** + * This is some form of representation of what the analyzer could deduce. + * We should probably have a chat about how this can best be conveyed to + * the frontend. It'll probably contain a (possibly incomplete) entity, plus + * enough info for the frontend to know what form data to show to the user + * for overriding/completing the info. + * @public + */ +export type AnalyzeLocationGenerateEntity = { + // Some form of partial representation of the entity + entity: RecursivePartial; + // Lists the suggestions that the user may want to override + fields: AnalyzeLocationEntityField[]; +}; + +// This is where I get really vague. Something like this perhaps? Or it could be +// something like a json-schema that contains enough info for the frontend to +// be able to present a form and explanations +/** @public */ +export type AnalyzeLocationEntityField = { + /** + * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the + * entity again if the user wants to change it + */ + field: string; + + /** The outcome of the analysis for this particular field */ + state: + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; + + // If the analysis did suggest a value, this is where it would be. Not sure if we want + // to limit this to strings or if we want it to be any JsonValue + value: string | null; + /** + * A text to show to the user to inform about the choices made. Like, it could say + * "Found a CODEOWNERS file that covers this target, so we suggest leaving this + * field empty; which would currently make it owned by X" where X is taken from the + * codeowners file. + */ + description: string; +}; diff --git a/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts b/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts new file mode 100644 index 0000000000..ab8d50534e --- /dev/null +++ b/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { RecursivePartial } from './RecursivePartial'; + +describe('RecursivePartial', () => { + it('is recursive', () => { + type X = { + required: { + required: string; + }; + }; + const x: RecursivePartial = { + required: {}, + }; + expect(x).toEqual({ required: {} }); + }); +}); diff --git a/plugins/catalog-common/src/ingestion/RecursivePartial.ts b/plugins/catalog-common/src/ingestion/RecursivePartial.ts new file mode 100644 index 0000000000..c452836f34 --- /dev/null +++ b/plugins/catalog-common/src/ingestion/RecursivePartial.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * Makes all keys of an entire hierarchy optional. + * @ignore + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; +}; diff --git a/plugins/catalog-common/src/ingestion/index.ts b/plugins/catalog-common/src/ingestion/index.ts new file mode 100644 index 0000000000..ceb2ff1dfc --- /dev/null +++ b/plugins/catalog-common/src/ingestion/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { + AnalyzeLocationResponse, + AnalyzeLocationRequest, + AnalyzeLocationExistingEntity, +} from './LocationAnalyzer'; diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 055955c9c7..224a30bdb2 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,7 +40,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/integration-react": "workspace:^", - "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index d019b40045..0663122253 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -30,7 +30,7 @@ import { Base64 } from 'js-base64'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; import { getBranchName, getCatalogFilename } from '../components/helpers'; -import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; /** diff --git a/yarn.lock b/yarn.lock index 5563c9ffbe..71d139f97d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4718,7 +4718,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: + "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" languageName: unknown @@ -4799,7 +4801,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" - "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 From 148d33fa855df14ddac45a10a5c99693e6833c00 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 5 Oct 2022 14:09:22 +0200 Subject: [PATCH 055/160] add api-reports Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 39 ++----------------- plugins/catalog-common/api-report.md | 38 ++++++++++++++++++ plugins/catalog-common/src/ingestion/index.ts | 2 + 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index ede024e296..f5922d51ab 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,6 +5,9 @@ ```ts /// +import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; @@ -51,42 +54,6 @@ import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { Validators } from '@backstage/catalog-model'; -// @public (undocumented) -export type AnalyzeLocationEntityField = { - field: string; - state: - | 'analysisSuggestedValue' - | 'analysisSuggestedNoValue' - | 'needsUserInput'; - value: string | null; - description: string; -}; - -// @public -export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; -}; - -// @public -export type AnalyzeLocationGenerateEntity = { - entity: RecursivePartial; - fields: AnalyzeLocationEntityField[]; -}; - -// @public (undocumented) -export type AnalyzeLocationRequest = { - location: LocationSpec; - catalogFilename?: string; -}; - -// @public (undocumented) -export type AnalyzeLocationResponse = { - existingEntityFiles: AnalyzeLocationExistingEntity[]; - generateEntities: AnalyzeLocationGenerateEntity[]; -}; - // @public (undocumented) export type AnalyzeOptions = { url: string; diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index b7eaf99d8b..867454f9bd 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -4,9 +4,47 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; +import { Entity } from '@backstage/catalog-model'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; import { ResourcePermission } from '@backstage/plugin-permission-common'; +// @public (undocumented) +export type AnalyzeLocationEntityField = { + field: string; + state: + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; + value: string | null; + description: string; +}; + +// @public +export type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +// @public +export type AnalyzeLocationGenerateEntity = { + entity: RecursivePartial; + fields: AnalyzeLocationEntityField[]; +}; + +// @public (undocumented) +export type AnalyzeLocationRequest = { + location: LocationSpec; + catalogFilename?: string; +}; + +// @public (undocumented) +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + // @alpha export const catalogEntityCreatePermission: BasicPermission; diff --git a/plugins/catalog-common/src/ingestion/index.ts b/plugins/catalog-common/src/ingestion/index.ts index ceb2ff1dfc..aced124bb4 100644 --- a/plugins/catalog-common/src/ingestion/index.ts +++ b/plugins/catalog-common/src/ingestion/index.ts @@ -18,4 +18,6 @@ export type { AnalyzeLocationResponse, AnalyzeLocationRequest, AnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField, } from './LocationAnalyzer'; From 823acaa88bd450a25a290f017b9e98a5965f1b69 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 5 Oct 2022 17:25:40 +0200 Subject: [PATCH 056/160] add changeset Signed-off-by: Kiss Miklos --- .changeset/five-tables-grow.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/five-tables-grow.md diff --git a/.changeset/five-tables-grow.md b/.changeset/five-tables-grow.md new file mode 100644 index 0000000000..982b17b97b --- /dev/null +++ b/.changeset/five-tables-grow.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-common': patch +--- + +Moved the following types from `@backstage/plugin-catalog-backend` to this package. + +- AnalyzeLocationResponse +- AnalyzeLocationRequest +- AnalyzeLocationExistingEntity +- AnalyzeLocationGenerateEntity +- AnalyzeLocationEntityField From dfe0fef06e5a95312c06fe66b8b56c9d81365839 Mon Sep 17 00:00:00 2001 From: Bret Hubbard Date: Wed, 5 Oct 2022 10:19:16 -0600 Subject: [PATCH 057/160] add instructions for a multi-stage build dockerfile Signed-off-by: Bret Hubbard --- docs/tutorials/yarn-migration.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/tutorials/yarn-migration.md b/docs/tutorials/yarn-migration.md index 7f898c7b98..6dee343e78 100644 --- a/docs/tutorials/yarn-migration.md +++ b/docs/tutorials/yarn-migration.md @@ -67,6 +67,13 @@ COPY .yarn ./.yarn COPY .yarnrc.yml ./ ``` +In a multi-stage `Dockerfile`, each stage that runs a `yarn` command will also need the Yarn 3 installation. For example, in the final stage you may need to add the following: + +```Dockerfile +COPY --from=build --chown=node:node /app/.yarn ./.yarn +COPY --from=build --chown=node:node /app/.yarnrc.yml ./ +``` + The `--production` flag to `yarn install` has been removed in Yarn 3, instead you need to use `yarn workspaces focus --all --production` to avoid installing development dependencies in your production deployment. A tradeoff of this is that `yarn workspaces focus` does not support the `--immutable` flag. ```Dockerfile From bde1e8c8e2dd3de13aa48f36f7dc7f57cd13e43c Mon Sep 17 00:00:00 2001 From: andrmaz <60042277+andrmaz@users.noreply.github.com> Date: Thu, 6 Oct 2022 00:00:46 +0200 Subject: [PATCH 058/160] feat(curve): add curve filter to graph page Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- .changeset/clever-clocks-drive.md | 6 +++ .../DependencyGraph/DependencyGraph.tsx | 11 +++++ .../src/components/DependencyGraph/Edge.tsx | 19 ++++--- .../src/components/DependencyGraph/types.ts | 6 +++ .../CatalogGraphPage/CatalogGraphPage.tsx | 7 +++ .../CatalogGraphPage/CurveFilter.test.tsx | 47 ++++++++++++++++++ .../CatalogGraphPage/CurveFilter.tsx | 49 +++++++++++++++++++ .../CatalogGraphPage/useCatalogGraphPage.ts | 13 ++++- .../EntityRelationsGraph.tsx | 5 +- .../components/EntityRelationsGraph/index.ts | 1 + .../components/EntityRelationsGraph/types.ts | 6 +++ 11 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 .changeset/clever-clocks-drive.md create mode 100644 plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx create mode 100644 plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx diff --git a/.changeset/clever-clocks-drive.md b/.changeset/clever-clocks-drive.md new file mode 100644 index 0000000000..d91a8ee53f --- /dev/null +++ b/.changeset/clever-clocks-drive.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-catalog-graph': patch +--- + +Added select to toggle the curve factory of the dependency graph diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 9f447f80cc..3457a83d7d 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -30,6 +30,7 @@ import { RenderNodeFunction, RenderLabelFunction, LabelPosition, + Curve, } from './types'; import { Node } from './Node'; import { Edge, GraphEdge } from './Edge'; @@ -162,6 +163,14 @@ export interface DependencyGraphProps * Default: `enabled` */ zoom?: 'enabled' | 'disabled' | 'enable-on-click'; + /** + * A factory for curve generators addressing both lines and areas. + * + * @remarks + * + * Default: 'curveMonotoneX' + */ + curve?: Curve; } const WORKSPACE_ID = 'workspace'; @@ -194,6 +203,7 @@ export function DependencyGraph( renderLabel, defs, zoom = 'enabled', + curve = 'curveMonotoneX', ...svgProps } = props; const theme: BackstageTheme = useTheme(); @@ -424,6 +434,7 @@ export function DependencyGraph( setEdge={setEdge} render={renderLabel} edge={edge} + curve={curve} /> ); })} diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index 72a7547cda..ea526827bf 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -24,6 +24,7 @@ import { RenderLabelFunction, DependencyEdge, LabelPosition, + Curve, } from './types'; import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; import { DefaultLabel } from './DefaultLabel'; @@ -70,23 +71,19 @@ export type EdgeComponentProps = { id: dagre.Edge, edge: DependencyEdge, ) => dagre.graphlib.Graph<{}>; + curve: Curve; }; const renderDefault = (props: RenderLabelProps) => ( ); -const createPath = d3Shape - .line() - .x(d => d.x) - .y(d => d.y) - .curve(d3Shape.curveStepBefore); - export function Edge({ render = renderDefault, setEdge, id, edge, + curve, }: EdgeComponentProps) { const { x = 0, y = 0, width, height, points } = edge; const labelProps: DependencyEdge = edge; @@ -114,6 +111,16 @@ export function Edge({ let path: string = ''; + const createPath = React.useMemo( + () => + d3Shape + .line() + .x(d => d.x) + .y(d => d.y) + .curve(d3Shape[curve]), + [curve], + ); + if (points) { const finitePoints = points.filter( (point: EdgePoint) => isFinite(point.x) && isFinite(point.y), diff --git a/packages/core-components/src/components/DependencyGraph/types.ts b/packages/core-components/src/components/DependencyGraph/types.ts index 4ae6057bd7..f74374957b 100644 --- a/packages/core-components/src/components/DependencyGraph/types.ts +++ b/packages/core-components/src/components/DependencyGraph/types.ts @@ -75,6 +75,12 @@ export type RenderNodeFunction = ( props: RenderNodeProps, ) => React.ReactNode; +/** + * @see {@link @types/d3-shape/index.d.ts#curveMonotoneX} + * @see {@link @types/d3-shape/index.d.ts#curveStepBefore} + */ +export type Curve = 'curveStepBefore' | 'curveMonotoneX'; + /** * Graph direction * diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 8e59c127d9..38fcdad918 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -35,11 +35,13 @@ import React, { MouseEvent, useCallback } from 'react'; import { useNavigate } from 'react-router'; import { ALL_RELATION_PAIRS, + Curve, Direction, EntityNode, EntityRelationsGraph, RelationPairs, } from '../EntityRelationsGraph'; +import { CurveFilter } from './CurveFilter'; import { DirectionFilter } from './DirectionFilter'; import { MaxDepthFilter } from './MaxDepthFilter'; import { SelectedKindsFilter } from './SelectedKindsFilter'; @@ -110,6 +112,7 @@ export const CatalogGraphPage = (props: { mergeRelations?: boolean; direction?: Direction; showFilters?: boolean; + curve?: Curve; }; }) => { const { relationPairs = ALL_RELATION_PAIRS, initialState } = props; @@ -130,6 +133,8 @@ export const CatalogGraphPage = (props: { setMergeRelations, direction, setDirection, + curve, + setCurve, rootEntityNames, setRootEntityNames, showFilters, @@ -201,6 +206,7 @@ export const CatalogGraphPage = (props: { relationPairs={relationPairs} /> + diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx new file mode 100644 index 0000000000..0d1ed6e9a7 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { CurveFilter } from './CurveFilter'; + +describe('', () => { + test('should display current curve label', () => { + const onChange = jest.fn(); + const { getByText } = render( + , + ); + + expect(getByText('Monotone X')).toBeInTheDocument(); + }); + + test('should select an alternative curve factory', async () => { + const onChange = jest.fn(); + const { getByText, getByTestId } = render( + , + ); + + expect(getByText('Step Before')).toBeInTheDocument(); + + await userEvent.click(getByTestId('select')); + await userEvent.click(getByText('Monotone X')); + + await waitFor(() => { + expect(getByText('Monotone X')).toBeInTheDocument(); + expect(onChange).toBeCalledWith('curveMonotoneX'); + }); + }); +}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx new file mode 100644 index 0000000000..b508d48172 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -0,0 +1,49 @@ +/* + * 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 { Select } from '@backstage/core-components'; +import { Box } from '@material-ui/core'; +import React, { useCallback } from 'react'; +import { Curve } from '../EntityRelationsGraph'; + +const CURVE_DISPLAY_NAMES: Record = { + curveMonotoneX: 'Monotone X', + curveStepBefore: 'Step Before', +}; + +export type Props = { + value: Curve; + onChange: (value: Curve) => void; +}; + +const curves: Curve[] = ['curveMonotoneX', 'curveStepBefore']; + +export const CurveFilter = ({ value, onChange }: Props) => { + const handleChange = useCallback(v => onChange(v as Curve), [onChange]); + + return ( + + } + value={value} + onChange={handleChange} + > + {[...new Set([DEFAULT_PAGE_LIMIT, ...options])] + .sort((a, b) => a - b) + .map(option => ( + + {option} + + ))} + + + ); +}; + +/** + * Props for {@link SearchResultLimiter}. + * @public + */ +export type SearchResultLimiterProps = Omit< + SearchResultLimiterBaseProps, + 'value' | 'onChange' +>; + +/** + * A component for setting the search context page limit. + * @param props - See {@link SearchResultLimiterProps}. + * @public + */ +export const SearchResultLimiter = (props: SearchResultLimiterProps) => { + const { pageLimit, setPageLimit } = useSearch(); + + const handleChange = useCallback( + (newPageLimit: number) => { + setPageLimit(newPageLimit); + }, + [setPageLimit], + ); + + return ( + + ); +}; diff --git a/plugins/search-react/src/components/SearchResultLimiter/index.ts b/plugins/search-react/src/components/SearchResultLimiter/index.ts new file mode 100644 index 0000000000..411e48b5f5 --- /dev/null +++ b/plugins/search-react/src/components/SearchResultLimiter/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './SearchResultLimiter'; diff --git a/plugins/search-react/src/components/index.ts b/plugins/search-react/src/components/index.ts index 62d8b611a4..524b0f9194 100644 --- a/plugins/search-react/src/components/index.ts +++ b/plugins/search-react/src/components/index.ts @@ -20,6 +20,7 @@ export * from './SearchAutocomplete'; export * from './SearchFilter'; export * from './SearchResult'; export * from './SearchResultPager'; +export * from './SearchResultLimiter'; export * from './SearchResultList'; export * from './SearchResultGroup'; export * from './DefaultResultListItem'; From db63ce8b07cc52083ba8da6708a4e0b41b6eadda Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 6 Oct 2022 09:36:54 +0100 Subject: [PATCH 062/160] Rename schema to paramsSchema Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 2 +- .../src/permissions/rules/createPropertyRule.ts | 2 +- .../src/permissions/rules/hasAnnotation.ts | 2 +- plugins/catalog-backend/src/permissions/rules/hasLabel.ts | 2 +- .../catalog-backend/src/permissions/rules/isEntityKind.ts | 2 +- .../src/permissions/rules/isEntityOwner.ts | 2 +- plugins/catalog-backend/src/service/createRouter.test.ts | 2 +- .../src/service/PermissionIntegrationClient.test.ts | 4 ++-- .../src/integration/createConditionExports.test.ts | 4 ++-- .../src/integration/createConditionFactory.test.ts | 2 +- .../src/integration/createConditionTransformer.test.ts | 4 ++-- .../src/integration/createConditionTransformer.ts | 4 ++-- .../integration/createPermissionIntegrationRouter.test.ts | 8 ++++---- .../src/integration/createPermissionIntegrationRouter.ts | 6 +++--- plugins/permission-node/src/integration/util.test.ts | 4 ++-- plugins/permission-node/src/types.ts | 2 +- plugins/playlist-backend/src/permissions/rules.ts | 4 ++-- 17 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 41c9874590..18e8afa947 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -20,7 +20,7 @@ The API has now changed to expect the parameters as a single object ```ts createPermissionRule({ - schema: z.object({ + paramSchema: z.object({ foo: z.string().describe('Foo value to match'), bar: z.string().describe('Bar value to match'), }), diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 29ce67ad53..19a4423846 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -24,7 +24,7 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') => name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.object({ + paramsSchema: z.object({ key: z .string() .describe(`Property within the entities ${propertyType} to match on`), diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index c8450f3d08..c24620f41c 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -31,7 +31,7 @@ export const hasAnnotation = createCatalogPermissionRule({ description: 'Allow entities which are annotated with the specified annotation', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.object({ + paramsSchema: z.object({ annotation: z.string().describe('Name of the annotation to match on'), value: z .string() diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 6b7b9d9da0..376534ca6f 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -27,7 +27,7 @@ export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.object({ + paramsSchema: z.object({ label: z.string().describe('Name of the label to match one'), }), apply: (resource, { label }) => diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index d64b357d9f..eee7a790b2 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -27,7 +27,7 @@ export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.object({ + paramsSchema: z.object({ kinds: z .array(z.string()) .describe('List of kinds to match at least one of'), diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 824e5e9ca6..c3bfff5c2e 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -29,7 +29,7 @@ export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.object({ + paramsSchema: z.object({ claims: z .array(z.string()) .describe( diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 088e6c71e2..f383df7364 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -696,7 +696,7 @@ describe('NextRouter permissioning', () => { name: 'FAKE_RULE', description: 'fake rule', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), }), apply: () => true, diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index 8cbeeaebd9..80782848e7 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -284,7 +284,7 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ input: z.enum(['yes', 'no']), }), apply: (_resource, { input }) => input === 'yes', @@ -297,7 +297,7 @@ describe('PermissionIntegrationClient', () => { description: 'Test rule 2', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ input: z.enum(['yes', 'no']), }), apply: (_resource, { input }) => input === 'yes', diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index 59639f5119..424b3c3c24 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -31,7 +31,7 @@ const testIntegration = () => name: 'testRule1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), bar: z.number(), }), @@ -45,7 +45,7 @@ const testIntegration = () => name: 'testRule2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), }), apply: (_resource: any) => false, diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 191688f0dd..2b3acff342 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -23,7 +23,7 @@ describe('createConditionFactory', () => { name: 'test-rule', description: 'test-description', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), }), apply: (_resource, _params) => true, diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 0544ff3ef8..a5507f0b44 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -27,7 +27,7 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), bar: z.number(), }), @@ -38,7 +38,7 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), }), apply: jest.fn(), diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index afbd4a9316..dc0b11e90e 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -47,9 +47,9 @@ const mapConditions = ( } const rule = getRule(criteria.rule); - const result = rule.schema.safeParse(criteria.params); + const result = rule.paramsSchema.safeParse(criteria.params); - if (rule.schema && !result.success) { + if (rule.paramsSchema && !result.success) { throw new InputError(`Parameters to rule are invalid`, result.error); } diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index adfa57b863..55d4d5f4cf 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -40,7 +40,7 @@ const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string(), bar: z.number().describe('bar'), }), @@ -52,7 +52,7 @@ const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.object({ + paramsSchema: z.object({ foo: z.string().describe('foo'), }), apply: (_resource: any, _foo) => false, @@ -603,7 +603,7 @@ describe('createPermissionIntegrationRouter', () => { name: testRule1.name, description: testRule1.description, resourceType: testRule1.resourceType, - schema: { + paramsSchema: { $schema: 'http://json-schema.org/draft-07/schema#', additionalProperties: false, properties: { @@ -623,7 +623,7 @@ describe('createPermissionIntegrationRouter', () => { name: testRule2.name, description: testRule2.description, resourceType: testRule2.resourceType, - schema: { + paramsSchema: { $schema: 'http://json-schema.org/draft-07/schema#', additionalProperties: false, properties: { diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 4f98597e6c..c2d99ac4a4 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -127,9 +127,9 @@ const applyConditions = ( } const rule = getRule(criteria.rule); - const result = rule.schema.safeParse(criteria.params); + const result = rule.paramsSchema.safeParse(criteria.params); - if (rule.schema && !result.success) { + if (rule.paramsSchema && !result.success) { throw new InputError(`Parameters to rule are invalid`, result.error); } @@ -195,7 +195,7 @@ export const createPermissionIntegrationRouter = < name: rule.name, description: rule.description, resourceType: rule.resourceType, - schema: zodToJsonSchema(rule.schema), + paramsSchema: zodToJsonSchema(rule.paramsSchema), })); return res.json({ permissions, rules: serializableRules }); diff --git a/plugins/permission-node/src/integration/util.test.ts b/plugins/permission-node/src/integration/util.test.ts index 924ab30757..b1961ae532 100644 --- a/plugins/permission-node/src/integration/util.test.ts +++ b/plugins/permission-node/src/integration/util.test.ts @@ -31,7 +31,7 @@ describe('permission integration utils', () => { name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - schema: z.object({}), + paramsSchema: z.object({}), apply: jest.fn(), toQuery: jest.fn(), }); @@ -40,7 +40,7 @@ describe('permission integration utils', () => { name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - schema: z.object({}), + paramsSchema: z.object({}), apply: jest.fn(), toQuery: jest.fn(), }); diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index b16c0cac32..df21889fb9 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -66,7 +66,7 @@ export type PermissionRule< /** * A ZodSchema that documents the parameters that this rule accepts. */ - schema: PermissionRuleSchema; + paramsSchema: PermissionRuleSchema; /** * Apply this rule to a resource already loaded from a backing data source. The params are diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index bebb7905b4..cd6711befb 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -33,7 +33,7 @@ const isOwner = createPlaylistPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - schema: z.object({ + paramsSchema: z.object({ owners: z.array(z.string()).describe('List of owner entity refs'), }), apply: (list: PlaylistMetadata, { owners }) => owners.includes(list.owner), @@ -47,7 +47,7 @@ const isPublic = createPlaylistPermissionRule({ name: 'IS_PUBLIC', description: 'Should allow only if the playlist is public', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - schema: z.object({}), + paramsSchema: z.object({}), apply: (list: PlaylistMetadata) => list.public, toQuery: () => ({ key: 'public', values: [true] }), }); From a3fef466ef733370e55fbb7fc1bc1e36c3fbb0bf Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 6 Oct 2022 10:06:42 +0100 Subject: [PATCH 063/160] Updated API reports Signed-off-by: Harry Hogg --- plugins/permission-node/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 5168eefebc..b272aa9b1c 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -177,7 +177,7 @@ export type PermissionRule< name: string; description: string; resourceType: TResourceType; - schema: PermissionRuleSchema; + paramsSchema: PermissionRuleSchema; apply( resource: TResource, params: NoInfer>>, From fa40df2bc7f0f42173fbcdfc2b1f6cc22afd444e Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Thu, 6 Oct 2022 12:04:31 +0100 Subject: [PATCH 064/160] Made changs to allow params and schemas to be defaulted and required only when there is params defined. Co-authored-by: Vincenzo Scamporlino Co-authored-by: Mike Lewis Signed-off-by: Harry Hogg --- plugins/catalog-backend/api-report.md | 2 +- plugins/permission-common/api-report.md | 6 ++- plugins/permission-common/src/types/api.ts | 6 ++- plugins/permission-node/api-report.md | 12 ++--- .../src/integration/createConditionExports.ts | 4 +- .../createConditionFactory.test.ts | 1 + .../src/integration/createConditionFactory.ts | 26 ++++++----- .../integration/createConditionTransformer.ts | 6 +-- .../createPermissionIntegrationRouter.test.ts | 44 ++----------------- .../createPermissionIntegrationRouter.ts | 10 ++--- .../src/integration/createPermissionRule.ts | 4 +- .../src/integration/util.test.ts | 3 -- plugins/permission-node/src/types.ts | 4 +- plugins/playlist-backend/api-report.md | 2 +- .../DefaultPlaylistPermissionPolicy.test.ts | 4 +- .../DefaultPlaylistPermissionPolicy.ts | 2 +- .../playlist-backend/src/permissions/rules.ts | 1 - 17 files changed, 53 insertions(+), 84 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index ea9b0b2277..6935243cf5 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -303,7 +303,7 @@ export const createCatalogConditionalDecision: ( // @alpha export const createCatalogPermissionRule: < - TParams extends PermissionRuleParams = PermissionRuleParams, + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => PermissionRule; diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index b51b82c907..1c2476d20d 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -177,7 +177,7 @@ export type PermissionCondition< > = { resourceType: TResourceType; rule: string; - params: TParams; + params?: TParams; }; // @public @@ -208,7 +208,9 @@ export type PermissionMessageBatch = { export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; // @public -export type PermissionRuleParams = Record; +export type PermissionRuleParams = + | undefined + | Record; // @public export type PolicyDecision = diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index 77d6206c07..5e7a2bb349 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -106,7 +106,7 @@ export type PermissionCondition< > = { resourceType: TResourceType; rule: string; - params: TParams; + params?: TParams; }; /** @@ -161,7 +161,9 @@ export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; * * @public */ -export type PermissionRuleParams = Record; +export type PermissionRuleParams = + | undefined + | Record; /** * An individual request sent to the permission backend. diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index b272aa9b1c..4bc654cd73 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -55,7 +55,9 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (params: TParams) => PermissionCondition + ? undefined extends TParams + ? () => PermissionCondition + : (params: TParams) => PermissionCondition : never; // @public @@ -98,7 +100,7 @@ export const createConditionFactory: < TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, -) => (params: TParams) => PermissionCondition; +) => (args_0: TParams) => PermissionCondition; // @public export const createConditionTransformer: < @@ -129,7 +131,7 @@ export const createPermissionRule: < TResource, TQuery, TResourceType extends string, - TParams extends PermissionRuleParams = PermissionRuleParams, + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => PermissionRule; @@ -154,7 +156,7 @@ export const makeCreatePermissionRule: < TResource, TQuery, TResourceType extends string, ->() => ( +>() => ( rule: PermissionRule, ) => PermissionRule; @@ -177,7 +179,7 @@ export type PermissionRule< name: string; description: string; resourceType: TResourceType; - paramsSchema: PermissionRuleSchema; + paramsSchema?: PermissionRuleSchema; apply( resource: TResource, params: NoInfer>>, diff --git a/plugins/permission-node/src/integration/createConditionExports.ts b/plugins/permission-node/src/integration/createConditionExports.ts index 3921c2dfe3..1c82434add 100644 --- a/plugins/permission-node/src/integration/createConditionExports.ts +++ b/plugins/permission-node/src/integration/createConditionExports.ts @@ -36,7 +36,9 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (params: TParams) => PermissionCondition + ? undefined extends TParams + ? () => PermissionCondition + : (params: TParams) => PermissionCondition : never; /** diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 2b3acff342..520fcc01f9 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -37,6 +37,7 @@ describe('createConditionFactory', () => { describe('return value', () => { it('constructs a condition with the rule name and supplied params', () => { const conditionFactory = createConditionFactory(testRule); + expect( conditionFactory({ foo: 'bar', diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 8edeb6230f..3a31e07dac 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -36,15 +36,17 @@ import { PermissionRule } from '../types'; * * @public */ -export const createConditionFactory = - < - TResourceType extends string, - TParams extends PermissionRuleParams = PermissionRuleParams, - >( - rule: PermissionRule, - ) => - (params: TParams): PermissionCondition => ({ - rule: rule.name, - resourceType: rule.resourceType, - params, - }); +export const createConditionFactory = < + TResourceType extends string, + TParams extends PermissionRuleParams = PermissionRuleParams, +>( + rule: PermissionRule, +) => { + return (...args: [TParams]): PermissionCondition => { + return { + rule: rule.name, + resourceType: rule.resourceType, + params: args[0], + }; + }; +}; diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index dc0b11e90e..4b6f933f79 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -47,13 +47,13 @@ const mapConditions = ( } const rule = getRule(criteria.rule); - const result = rule.paramsSchema.safeParse(criteria.params); + const result = rule.paramsSchema?.safeParse(criteria.params); - if (rule.paramsSchema && !result.success) { + if (result && !result.success) { throw new InputError(`Parameters to rule are invalid`, result.error); } - return rule.toQuery(criteria.params); + return rule.toQuery(criteria.params ?? {}); }; /** diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 55d4d5f4cf..32d814465f 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -52,11 +52,8 @@ const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - paramsSchema: z.object({ - foo: z.string().describe('foo'), - }), - apply: (_resource: any, _foo) => false, - toQuery: _foo => ({}), + apply: (_resource: any) => false, + toQuery: () => ({}), }); describe('createPermissionIntegrationRouter', () => { @@ -105,7 +102,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { foo: 'b' }, }, ], }, @@ -113,9 +109,6 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'c', - }, }, }, { @@ -133,9 +126,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'b', - }, }, ], }, @@ -153,9 +143,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'c', - }, }, ], }, @@ -191,9 +178,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'a', - }, }, { allOf: [ @@ -208,7 +192,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { foo: 'b' }, }, ], }, @@ -227,9 +210,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'b', - }, }, ], }, @@ -248,9 +228,6 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'd', - }, }, }, ], @@ -309,9 +286,6 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'b', - }, }, }, { @@ -337,9 +311,6 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'c', - }, }, }, }, @@ -360,9 +331,6 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: { - foo: 'd', - }, }, ], }, @@ -626,13 +594,7 @@ describe('createPermissionIntegrationRouter', () => { paramsSchema: { $schema: 'http://json-schema.org/draft-07/schema#', additionalProperties: false, - properties: { - foo: { - description: 'foo', - type: 'string', - }, - }, - required: ['foo'], + properties: {}, type: 'object', }, }, diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index c2d99ac4a4..52f34d7e6a 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -47,7 +47,7 @@ const permissionCriteriaSchema: z.ZodSchema< z.object({ rule: z.string(), resourceType: z.string(), - params: z.record(z.any()), + params: z.record(z.any()).optional(), }), ]), ); @@ -127,13 +127,13 @@ const applyConditions = ( } const rule = getRule(criteria.rule); - const result = rule.paramsSchema.safeParse(criteria.params); + const result = rule.paramsSchema?.safeParse(criteria.params); - if (rule.paramsSchema && !result.success) { + if (result && !result.success) { throw new InputError(`Parameters to rule are invalid`, result.error); } - return rule.apply(resource, criteria.params); + return rule.apply(resource, criteria.params ?? {}); }; /** @@ -195,7 +195,7 @@ export const createPermissionIntegrationRouter = < name: rule.name, description: rule.description, resourceType: rule.resourceType, - paramsSchema: zodToJsonSchema(rule.paramsSchema), + paramsSchema: zodToJsonSchema(rule.paramsSchema ?? z.object({})), })); return res.json({ permissions, rules: serializableRules }); diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index ea2e37a671..44187e58a6 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -26,7 +26,7 @@ export const createPermissionRule = < TResource, TQuery, TResourceType extends string, - TParams extends PermissionRuleParams = PermissionRuleParams, + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => rule; @@ -41,7 +41,7 @@ export const createPermissionRule = < */ export const makeCreatePermissionRule = () => - ( + ( rule: PermissionRule, ) => createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/util.test.ts b/plugins/permission-node/src/integration/util.test.ts index b1961ae532..6c1c270953 100644 --- a/plugins/permission-node/src/integration/util.test.ts +++ b/plugins/permission-node/src/integration/util.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { z } from 'zod'; import { createPermissionRule } from './createPermissionRule'; import { createGetRule, @@ -31,7 +30,6 @@ describe('permission integration utils', () => { name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - paramsSchema: z.object({}), apply: jest.fn(), toQuery: jest.fn(), }); @@ -40,7 +38,6 @@ describe('permission integration utils', () => { name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - paramsSchema: z.object({}), apply: jest.fn(), toQuery: jest.fn(), }); diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index df21889fb9..6b15df407e 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -64,9 +64,9 @@ export type PermissionRule< resourceType: TResourceType; /** - * A ZodSchema that documents the parameters that this rule accepts. + * A ZodSchema that reflects the structure of the parameters that are passed to */ - paramsSchema: PermissionRuleSchema; + paramsSchema?: PermissionRuleSchema; /** * Apply this rule to a resource already loaded from a backing data source. The params are diff --git a/plugins/playlist-backend/api-report.md b/plugins/playlist-backend/api-report.md index faae97a734..6b51f316c4 100644 --- a/plugins/playlist-backend/api-report.md +++ b/plugins/playlist-backend/api-report.md @@ -79,7 +79,7 @@ export const playlistConditions: Conditions<{ PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - PermissionRuleParams + undefined >; }>; diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts index 3d761bfd35..17ee4a840a 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts @@ -70,7 +70,7 @@ describe('DefaultPlaylistPermissionPolicy', () => { playlistConditions.isOwner({ owners: ['user:default/me', 'group:default/owner'], }), - playlistConditions.isPublic({}), + playlistConditions.isPublic(), ], }, }); @@ -91,7 +91,7 @@ describe('DefaultPlaylistPermissionPolicy', () => { playlistConditions.isOwner({ owners: ['user:default/me', 'group:default/owner'], }), - playlistConditions.isPublic({}), + playlistConditions.isPublic(), ], }, }); diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts index 317f5da9c8..b4556fbb4f 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts @@ -71,7 +71,7 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { playlistConditions.isOwner({ owners: user?.identity.ownershipEntityRefs ?? [], }), - playlistConditions.isPublic({}), + playlistConditions.isPublic(), ], }); } diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index cd6711befb..99f4725972 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -47,7 +47,6 @@ const isPublic = createPlaylistPermissionRule({ name: 'IS_PUBLIC', description: 'Should allow only if the playlist is public', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - paramsSchema: z.object({}), apply: (list: PlaylistMetadata) => list.public, toQuery: () => ({ key: 'public', values: [true] }), }); From 96e3bb375cffd491af67dce6729e11cfc671e486 Mon Sep 17 00:00:00 2001 From: Daniel Albuquerque <1187103+worldtiki@users.noreply.github.com> Date: Thu, 6 Oct 2022 16:57:46 +0100 Subject: [PATCH 065/160] Update writing-custom-actions.md Signed-off-by: Daniel Albuquerque <1187103+worldtiki@users.noreply.github.com> --- docs/features/software-templates/writing-custom-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 17a9029b2d..dfbd4b0a42 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -123,7 +123,7 @@ will set the available actions that the scaffolder has access to. ```ts import { createBuiltinActions } from '@backstage/plugin-scaffolder-backend'; import { ScmIntegrations } from '@backstage/integration'; -import { createNewFileAction } from './actions/custom'; +import { createNewFileAction } from './scaffolder/actions/custom'; export default async function createPlugin( env: PluginEnvironment, From 4cc3dccc26cdae0dc7f4d40b1b286459df236286 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 6 Oct 2022 18:14:52 +0200 Subject: [PATCH 066/160] deprecate ingestions types after moving to common Signed-off-by: Kiss Miklos --- .changeset/curvy-pets-wash.md | 8 +++ plugins/catalog-backend/api-report.md | 23 ++++++-- .../catalog-backend/src/ingestion/index.ts | 5 ++ .../catalog-backend/src/ingestion/types.ts | 53 +++++++++++++++++-- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md index a0e9571b18..4c824f9ef4 100644 --- a/.changeset/curvy-pets-wash.md +++ b/.changeset/curvy-pets-wash.md @@ -3,3 +3,11 @@ --- Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + +Moved the following types from this package to `@backstage/plugin-catalog-backend`. + +- AnalyzeLocationResponse +- AnalyzeLocationRequest +- AnalyzeLocationExistingEntity +- AnalyzeLocationGenerateEntity +- AnalyzeLocationEntityField diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f5922d51ab..d23c6f7519 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,9 +5,11 @@ ```ts /// -import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; -import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; -import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationEntityField as AnalyzeLocationEntityField_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationExistingEntity as AnalyzeLocationExistingEntity_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationGenerateEntity as AnalyzeLocationGenerateEntity_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationRequest as AnalyzeLocationRequest_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse as AnalyzeLocationResponse_2 } from '@backstage/plugin-catalog-common'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; @@ -54,6 +56,21 @@ import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { Validators } from '@backstage/catalog-model'; +// @public @deprecated +export type AnalyzeLocationEntityField = AnalyzeLocationEntityField_2; + +// @public @deprecated +export type AnalyzeLocationExistingEntity = AnalyzeLocationExistingEntity_2; + +// @public @deprecated +export type AnalyzeLocationGenerateEntity = AnalyzeLocationGenerateEntity_2; + +// @public @deprecated (undocumented) +export type AnalyzeLocationRequest = AnalyzeLocationRequest_2; + +// @public @deprecated (undocumented) +export type AnalyzeLocationResponse = AnalyzeLocationResponse_2; + // @public (undocumented) export type AnalyzeOptions = { url: string; diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 0fc71f6785..0b00809043 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -15,6 +15,11 @@ */ export type { + AnalyzeLocationEntityField, + AnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity, + AnalyzeLocationRequest, + AnalyzeLocationResponse, LocationAnalyzer, ScmLocationAnalyzer, AnalyzeOptions, diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index c6a480a7f5..467fe498b2 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -13,12 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { - AnalyzeLocationResponse, - AnalyzeLocationRequest, - AnalyzeLocationExistingEntity, + AnalyzeLocationRequest as ExaltedAnalyzeLocationRequest, + AnalyzeLocationResponse as ExaltedAnalyzeLocationResponse, + AnalyzeLocationExistingEntity as ExaltedAnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity as ExaltedAnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField as ExaltedAnalyzeLocationEntityField, } from '@backstage/plugin-catalog-common'; +/** + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationRequest = ExaltedAnalyzeLocationRequest; +/** + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationResponse = ExaltedAnalyzeLocationResponse; + +/** + * If the folder pointed to already contained catalog info yaml files, they are + * read and emitted like this so that the frontend can inform the user that it + * located them and can make sure to register them as well if they weren't + * already + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationExistingEntity = + ExaltedAnalyzeLocationExistingEntity; +/** + * This is some form of representation of what the analyzer could deduce. + * We should probably have a chat about how this can best be conveyed to + * the frontend. It'll probably contain a (possibly incomplete) entity, plus + * enough info for the frontend to know what form data to show to the user + * for overriding/completing the info. + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationGenerateEntity = + ExaltedAnalyzeLocationGenerateEntity; + +/** + * + * This is where I get really vague. Something like this perhaps? Or it could be + * something like a json-schema that contains enough info for the frontend to + * be able to present a form and explanations + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationEntityField = ExaltedAnalyzeLocationEntityField; + /** @public */ export type LocationAnalyzer = { /** @@ -31,7 +77,6 @@ export type LocationAnalyzer = { location: AnalyzeLocationRequest, ): Promise; }; - /** @public */ export type AnalyzeOptions = { url: string; From 7218a2b0ab0d863051309cfc3397433b9ac8e512 Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Thu, 6 Oct 2022 15:03:01 -0400 Subject: [PATCH 067/160] fix localKubectlProxy link the anchors for subheadings generated from this markdown are always lowercase, so this link wasn't working before Signed-off-by: Jamie Klassen --- docs/features/kubernetes/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 1dd684db5f..7dc0e326fa 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -62,7 +62,7 @@ This is an array used to determine where to retrieve cluster configuration from. Valid cluster locator methods are: - [`catalog`](#catalog) -- [`localKubectlProxy`](#localKubectlProxy) +- [`localKubectlProxy`](#localkubectlproxy) - [`config`](#config) - [`gke`](#gke) - [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier) From 58ecab4409f220c385805c4f2829f286464b7752 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 7 Oct 2022 12:45:24 +0200 Subject: [PATCH 068/160] fix(catalog/bitbucketCloud): fix changeset Change included at PR #13859 is rather a patch than a minor change. Signed-off-by: Patrick Jungermann --- .changeset/brown-grapes-battle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/brown-grapes-battle.md b/.changeset/brown-grapes-battle.md index f94c938b02..21b345fc83 100644 --- a/.changeset/brown-grapes-battle.md +++ b/.changeset/brown-grapes-battle.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch --- Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. From 64dea77ede13f98efb527d7ad8dfd1b6bb958ec7 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 12:52:33 +0200 Subject: [PATCH 069/160] move type to common package Signed-off-by: Kiss Miklos --- .../catalog-backend/src/ingestion/types.ts | 21 +++++------ plugins/catalog-common/api-report.md | 8 ++++- plugins/catalog-common/src/common.ts | 32 +++++++++++++++++ plugins/catalog-common/src/index.ts | 1 + .../src/ingestion/LocationAnalyzer.ts | 2 +- plugins/catalog-node/api-report.md | 35 +++++++++---------- plugins/catalog-node/package.json | 1 + plugins/catalog-node/src/api/common.ts | 8 ++--- .../catalog-node/src/api/processingResult.ts | 3 +- plugins/catalog-node/src/api/processor.ts | 3 +- yarn.lock | 1 + 11 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 plugins/catalog-common/src/common.ts diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 467fe498b2..416fa968ab 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -15,23 +15,23 @@ */ import { - AnalyzeLocationRequest as ExaltedAnalyzeLocationRequest, - AnalyzeLocationResponse as ExaltedAnalyzeLocationResponse, - AnalyzeLocationExistingEntity as ExaltedAnalyzeLocationExistingEntity, - AnalyzeLocationGenerateEntity as ExaltedAnalyzeLocationGenerateEntity, - AnalyzeLocationEntityField as ExaltedAnalyzeLocationEntityField, + AnalyzeLocationRequest as NonDeprecatedAnalyzeLocationRequest, + AnalyzeLocationResponse as NonDeprecatedAnalyzeLocationResponse, + AnalyzeLocationExistingEntity as NonDeprecatedAnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity as NonDeprecatedAnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField as NonDeprecatedAnalyzeLocationEntityField, } from '@backstage/plugin-catalog-common'; /** * @public * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type AnalyzeLocationRequest = ExaltedAnalyzeLocationRequest; +export type AnalyzeLocationRequest = NonDeprecatedAnalyzeLocationRequest; /** * @public * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type AnalyzeLocationResponse = ExaltedAnalyzeLocationResponse; +export type AnalyzeLocationResponse = NonDeprecatedAnalyzeLocationResponse; /** * If the folder pointed to already contained catalog info yaml files, they are @@ -42,7 +42,7 @@ export type AnalyzeLocationResponse = ExaltedAnalyzeLocationResponse; * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ export type AnalyzeLocationExistingEntity = - ExaltedAnalyzeLocationExistingEntity; + NonDeprecatedAnalyzeLocationExistingEntity; /** * This is some form of representation of what the analyzer could deduce. * We should probably have a chat about how this can best be conveyed to @@ -53,7 +53,7 @@ export type AnalyzeLocationExistingEntity = * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ export type AnalyzeLocationGenerateEntity = - ExaltedAnalyzeLocationGenerateEntity; + NonDeprecatedAnalyzeLocationGenerateEntity; /** * @@ -63,7 +63,8 @@ export type AnalyzeLocationGenerateEntity = * @public * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type AnalyzeLocationEntityField = ExaltedAnalyzeLocationEntityField; +export type AnalyzeLocationEntityField = + NonDeprecatedAnalyzeLocationEntityField; /** @public */ export type LocationAnalyzer = { diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index 867454f9bd..1bef626c79 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -6,7 +6,6 @@ import { BasicPermission } from '@backstage/plugin-permission-common'; import { Entity } from '@backstage/catalog-model'; import { IndexableDocument } from '@backstage/plugin-search-common'; -import { LocationSpec } from '@backstage/plugin-catalog-node'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @public (undocumented) @@ -93,6 +92,13 @@ export const catalogPermissions: ( | ResourcePermission<'catalog-entity'> )[]; +// @public +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; + // @alpha export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; ``` diff --git a/plugins/catalog-common/src/common.ts b/plugins/catalog-common/src/common.ts new file mode 100644 index 0000000000..c926a94b28 --- /dev/null +++ b/plugins/catalog-common/src/common.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** + * Holds the entity location information. + * + * @remarks + * + * `presence` flag: when using repo importer plugin, location is being created before the component yaml file is merged to the main branch. + * This flag is then set to indicate that the file can be not present. + * default value: 'required'. + * + * @public + */ +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index a37fd122f9..95bd65bc92 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -36,3 +36,4 @@ export type { CatalogEntityPermission } from './permissions'; export * from './search'; export * from './ingestion'; +export type { LocationSpec } from './common'; diff --git a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts index 8d570894cf..08ae5ef0cb 100644 --- a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/plugin-catalog-node'; +import { LocationSpec } from '../common'; import { Entity } from '@backstage/catalog-model'; import { RecursivePartial } from './RecursivePartial'; diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index 525516be54..09a05ec7d9 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -10,6 +10,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { JsonValue } from '@backstage/types'; +import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @alpha (undocumented) @@ -31,7 +32,7 @@ export const catalogProcessingExtensionPoint: ExtensionPoint; preProcessEntity?( entity: Entity, - location: LocationSpec, + location: LocationSpec_2, emit: CatalogProcessorEmit, - originLocation: LocationSpec, + originLocation: LocationSpec_2, cache: CatalogProcessorCache, ): Promise; validateEntityKind?(entity: Entity): Promise; postProcessEntity?( entity: Entity, - location: LocationSpec, + location: LocationSpec_2, emit: CatalogProcessorEmit, cache: CatalogProcessorCache, ): Promise; @@ -66,26 +67,26 @@ export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; export type CatalogProcessorEntityResult = { type: 'entity'; entity: Entity; - location: LocationSpec; + location: LocationSpec_2; }; // @public (undocumented) export type CatalogProcessorErrorResult = { type: 'error'; error: Error; - location: LocationSpec; + location: LocationSpec_2; }; // @public (undocumented) export type CatalogProcessorLocationResult = { type: 'location'; - location: LocationSpec; + location: LocationSpec_2; }; // @public export type CatalogProcessorParser = (options: { data: Buffer; - location: LocationSpec; + location: LocationSpec_2; }) => AsyncIterable; // @public (undocumented) @@ -153,30 +154,26 @@ export type EntityRelationSpec = { target: CompoundEntityRef; }; -// @public -export type LocationSpec = { - type: string; - target: string; - presence?: 'optional' | 'required'; -}; +// @public @deprecated +export type LocationSpec = LocationSpec_2; // @public export const processingResult: Readonly<{ readonly notFoundError: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, message: string, ) => CatalogProcessorResult; readonly inputError: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, message: string, ) => CatalogProcessorResult; readonly generalError: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, message: string, ) => CatalogProcessorResult; - readonly location: (newLocation: LocationSpec) => CatalogProcessorResult; + readonly location: (newLocation: LocationSpec_2) => CatalogProcessorResult; readonly entity: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, newEntity: Entity, ) => CatalogProcessorResult; readonly relation: (spec: EntityRelationSpec) => CatalogProcessorResult; diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 0a7e687bb7..7cbbac1531 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -28,6 +28,7 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/types": "workspace:^" }, "devDependencies": { diff --git a/plugins/catalog-node/src/api/common.ts b/plugins/catalog-node/src/api/common.ts index f3b4a387ba..c91aeca2d9 100644 --- a/plugins/catalog-node/src/api/common.ts +++ b/plugins/catalog-node/src/api/common.ts @@ -15,6 +15,7 @@ */ import { CompoundEntityRef } from '@backstage/catalog-model'; +import { LocationSpec as NonDeprecatedLocationSpec } from '@backstage/plugin-catalog-common'; /** * Holds the entity location information. @@ -26,12 +27,9 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; * default value: 'required'. * * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type LocationSpec = { - type: string; - target: string; - presence?: 'optional' | 'required'; -}; +export type LocationSpec = NonDeprecatedLocationSpec; /** * Holds the relation data for entities. diff --git a/plugins/catalog-node/src/api/processingResult.ts b/plugins/catalog-node/src/api/processingResult.ts index 84f1f4b70d..d8b6f3ae02 100644 --- a/plugins/catalog-node/src/api/processingResult.ts +++ b/plugins/catalog-node/src/api/processingResult.ts @@ -17,7 +17,8 @@ import { InputError, NotFoundError } from '@backstage/errors'; import { Entity } from '@backstage/catalog-model'; import { CatalogProcessorResult } from './processor'; -import { EntityRelationSpec, LocationSpec } from './common'; +import { EntityRelationSpec } from './common'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; /** * Factory functions for the standard processing result types. diff --git a/plugins/catalog-node/src/api/processor.ts b/plugins/catalog-node/src/api/processor.ts index 44e8b298b0..3ad080f014 100644 --- a/plugins/catalog-node/src/api/processor.ts +++ b/plugins/catalog-node/src/api/processor.ts @@ -16,7 +16,8 @@ import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; -import { EntityRelationSpec, LocationSpec } from './common'; +import { EntityRelationSpec } from './common'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; /** * @public diff --git a/yarn.lock b/yarn.lock index 71d139f97d..aca25a583e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4838,6 +4838,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" "@backstage/types": "workspace:^" languageName: unknown linkType: soft From 45ac7769817b40ec5ce7f8827a66057c412def62 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 12:55:38 +0200 Subject: [PATCH 070/160] add docs string Signed-off-by: Kiss Miklos --- plugins/catalog-backend/src/ingestion/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 416fa968ab..78339b3b23 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -90,6 +90,7 @@ export type ScmLocationAnalyzer = { supports(url: string): boolean; /** This function can return an array of already existing entities */ analyze(options: AnalyzeOptions): Promise<{ + /** Existing entities in the analyzed location */ existing: AnalyzeLocationExistingEntity[]; }>; }; From 78e7698e4b70e1f07f031228ec5f754ea5dbdd5d Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Fri, 7 Oct 2022 12:08:00 +0100 Subject: [PATCH 071/160] Removed unnecessary tupling of params Signed-off-by: Harry Hogg --- .../permission-node/src/integration/createConditionFactory.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 3a31e07dac..7f8cb1ced8 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -42,11 +42,11 @@ export const createConditionFactory = < >( rule: PermissionRule, ) => { - return (...args: [TParams]): PermissionCondition => { + return (params: TParams): PermissionCondition => { return { rule: rule.name, resourceType: rule.resourceType, - params: args[0], + params, }; }; }; From 404366c8539d97c4b5215eb0b48d1ec0e709b2ea Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 14:31:55 +0200 Subject: [PATCH 072/160] add changeset Signed-off-by: Kiss Miklos --- .changeset/brave-goats-rush.md | 5 +++++ plugins/catalog-common/package.json | 1 - yarn.lock | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changeset/brave-goats-rush.md diff --git a/.changeset/brave-goats-rush.md b/.changeset/brave-goats-rush.md new file mode 100644 index 0000000000..12dae257af --- /dev/null +++ b/.changeset/brave-goats-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': patch +--- + +Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` and will be removed from this after some time. diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 96e9f31b43..a847d5766c 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -34,7 +34,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^" }, diff --git a/yarn.lock b/yarn.lock index aca25a583e..8afb919f99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4720,7 +4720,6 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" languageName: unknown From 9552527c37dfd48b410dc4f6c959a166ce7024ab Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 16:02:05 +0200 Subject: [PATCH 073/160] fix changeset Signed-off-by: Kiss Miklos --- .changeset/brave-goats-rush.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/brave-goats-rush.md b/.changeset/brave-goats-rush.md index 12dae257af..8a14f8ea00 100644 --- a/.changeset/brave-goats-rush.md +++ b/.changeset/brave-goats-rush.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-node': patch +'@backstage/plugin-catalog-node': minor --- -Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` and will be removed from this after some time. +Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. From eb25f7e12d99fbe175ac97d3b46e791c3508232a Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Fri, 7 Oct 2022 15:19:09 +0100 Subject: [PATCH 074/160] Broke up changesets to reflect the package changes Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 2 -- .changeset/quick-meals-talk.md | 19 ++++++++++++++++++ .changeset/seven-panthers-chew.md | 33 +++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .changeset/quick-meals-talk.md create mode 100644 .changeset/seven-panthers-chew.md diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index 18e8afa947..bdd6d1f594 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -1,8 +1,6 @@ --- -'@backstage/plugin-catalog-backend': minor '@backstage/plugin-permission-common': minor '@backstage/plugin-permission-node': minor -'@backstage/plugin-playlist-backend': minor --- **BREAKING**: When defining permission rules, it's now necessary to provide a ZodSchema that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. diff --git a/.changeset/quick-meals-talk.md b/.changeset/quick-meals-talk.md new file mode 100644 index 0000000000..bde70d25fd --- /dev/null +++ b/.changeset/quick-meals-talk.md @@ -0,0 +1,19 @@ +--- +'@backstage/plugin-playlist-backend': minor +--- + +**BREAKING** Due to the changes made in the Permission framework. The playlist backends permission rules have change to reflect this. + +As an example the `playlistConditions.isOwner` API has changed from + +```ts +playlistConditions.isOwner(['user:default/me', 'group:default/owner']); +``` + +to the new API + +```ts +playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], +}); +``` diff --git a/.changeset/seven-panthers-chew.md b/.changeset/seven-panthers-chew.md new file mode 100644 index 0000000000..7b44f2f633 --- /dev/null +++ b/.changeset/seven-panthers-chew.md @@ -0,0 +1,33 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +**BREAKING** Due to the changes made in the Permission framework. The catalogs permission rules and the API of `createCatalogPermissionRule` have been changed to reflect the change from individual function parameters to a single object parameter and the addition of the `paramsSchema`. + +As an example for the `hasLabel` rule. The API before the change was + +```ts +hasLabel.apply(entity, 'backstage.io/testLabel'); +hasLabel.toQuery('backstage.io/testLabel'); +``` + +and the API after the change now is + +```ts +hasLabel.apply(entity, { + label: 'backstage.io/testLabel', +}); + +hasLabel.toQuery({ + label: 'backstage.io/testLabel', +}); +``` + +This applies to all of the permission rules exported by the catalog backend. + +- `hasAnnotation` +- `hasLabel` +- `hasMetadata` +- `hasSpec` +- `isEntityKind` +- `isEntityOwner` From f6aa16fb7a3788f61919a959086f63853d580a49 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Fri, 7 Oct 2022 16:16:22 +0100 Subject: [PATCH 075/160] Updated API reports Signed-off-by: Harry Hogg --- plugins/permission-node/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 4bc654cd73..3895b4b3cb 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -100,7 +100,7 @@ export const createConditionFactory: < TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, -) => (args_0: TParams) => PermissionCondition; +) => (params: TParams) => PermissionCondition; // @public export const createConditionTransformer: < From 5eb598babb013462a7fc20719d82037574282de2 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 7 Oct 2022 17:38:42 +0200 Subject: [PATCH 076/160] refactor(search-react): restrict range of valid options type Signed-off-by: Camila Belo --- plugins/search-react/api-report.md | 10 ++++++++- .../SearchResultLimiter.tsx | 22 +++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 744f45065f..24b70240a0 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -335,11 +335,19 @@ export type SearchResultLimiterBaseProps = { id?: string; className?: string; label?: ReactNode; - options?: number[]; + options?: SearchResultLimiterOption[]; value?: number; onChange?: (value: number) => void; }; +// @public +export type SearchResultLimiterOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchResultLimiterOption; + // @public export type SearchResultLimiterProps = Omit< SearchResultLimiterBaseProps, diff --git a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx b/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx index 0d4b8381dd..7f2d1ffc58 100644 --- a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx +++ b/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx @@ -25,6 +25,17 @@ import { } from '@material-ui/core'; import { useSearch } from '../../context'; +/** + * A page limit option, this value must not be greater than 100. + * @public + */ +export type SearchResultLimiterOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchResultLimiterOption; + /** * Props for {@link SearchResultLimiterBase}. * @public @@ -39,7 +50,7 @@ export type SearchResultLimiterBaseProps = { /** * The combobox labels, defaults to 10, 25, 50 and 100. */ - options?: number[]; + options?: SearchResultLimiterOption[]; /** * Combobox selected option, defaults to 25; */ @@ -129,18 +140,11 @@ export type SearchResultLimiterProps = Omit< export const SearchResultLimiter = (props: SearchResultLimiterProps) => { const { pageLimit, setPageLimit } = useSearch(); - const handleChange = useCallback( - (newPageLimit: number) => { - setPageLimit(newPageLimit); - }, - [setPageLimit], - ); - return ( ); }; From d3891773a57dc9a5f45d1684f5e24000d3220f67 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:25:56 +0000 Subject: [PATCH 077/160] Update dependency @swc/core to v1.3.5 Signed-off-by: Renovate Bot --- storybook/yarn.lock | 110 ++++++++++++++++++++++---------------------- yarn.lock | 110 ++++++++++++++++++++++---------------------- 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/storybook/yarn.lock b/storybook/yarn.lock index db03614312..1567968127 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -2977,126 +2977,126 @@ __metadata: languageName: node linkType: hard -"@swc/core-android-arm-eabi@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm-eabi@npm:1.3.4" +"@swc/core-android-arm-eabi@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm-eabi@npm:1.3.5" dependencies: "@swc/wasm": 1.2.122 conditions: os=android & cpu=arm languageName: node linkType: hard -"@swc/core-android-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm64@npm:1.3.4" +"@swc/core-android-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-arm64@npm:1.3.4" +"@swc/core-darwin-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-arm64@npm:1.3.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-x64@npm:1.3.4" +"@swc/core-darwin-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-x64@npm:1.3.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-freebsd-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-freebsd-x64@npm:1.3.4" +"@swc/core-freebsd-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-freebsd-x64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.4" +"@swc/core-linux-arm-gnueabihf@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.4" +"@swc/core-linux-arm64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.4" +"@swc/core-linux-arm64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.4" +"@swc/core-linux-x64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-musl@npm:1.3.4" +"@swc/core-linux-x64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-musl@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.4" +"@swc/core-win32-arm64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.4" +"@swc/core-win32-ia32-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.4" +"@swc/core-win32-x64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.2.239": - version: 1.3.4 - resolution: "@swc/core@npm:1.3.4" + version: 1.3.5 + resolution: "@swc/core@npm:1.3.5" dependencies: - "@swc/core-android-arm-eabi": 1.3.4 - "@swc/core-android-arm64": 1.3.4 - "@swc/core-darwin-arm64": 1.3.4 - "@swc/core-darwin-x64": 1.3.4 - "@swc/core-freebsd-x64": 1.3.4 - "@swc/core-linux-arm-gnueabihf": 1.3.4 - "@swc/core-linux-arm64-gnu": 1.3.4 - "@swc/core-linux-arm64-musl": 1.3.4 - "@swc/core-linux-x64-gnu": 1.3.4 - "@swc/core-linux-x64-musl": 1.3.4 - "@swc/core-win32-arm64-msvc": 1.3.4 - "@swc/core-win32-ia32-msvc": 1.3.4 - "@swc/core-win32-x64-msvc": 1.3.4 + "@swc/core-android-arm-eabi": 1.3.5 + "@swc/core-android-arm64": 1.3.5 + "@swc/core-darwin-arm64": 1.3.5 + "@swc/core-darwin-x64": 1.3.5 + "@swc/core-freebsd-x64": 1.3.5 + "@swc/core-linux-arm-gnueabihf": 1.3.5 + "@swc/core-linux-arm64-gnu": 1.3.5 + "@swc/core-linux-arm64-musl": 1.3.5 + "@swc/core-linux-x64-gnu": 1.3.5 + "@swc/core-linux-x64-musl": 1.3.5 + "@swc/core-win32-arm64-msvc": 1.3.5 + "@swc/core-win32-ia32-msvc": 1.3.5 + "@swc/core-win32-x64-msvc": 1.3.5 dependenciesMeta: "@swc/core-android-arm-eabi": optional: true @@ -3126,7 +3126,7 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: 24b4ca4a096fea53056ed227a8aa09aa38cfe5eef344451397e66a2d183ded119872cf4fc8c671c0a6eb34985537cbf8d8a7e742b9e27dad0735426693d11dc8 + checksum: f0bcd0aa1d1a0b7d48ee5c416f8a384e19e4c0eed1e0be940d8893bf3981d98bf00f27d03bebf9cdedd34bf316d68c2db68e7bb7d1940d7b6062541e6e4cc738 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index f6bfb7f350..9cb701d697 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12515,126 +12515,126 @@ __metadata: languageName: node linkType: hard -"@swc/core-android-arm-eabi@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm-eabi@npm:1.3.4" +"@swc/core-android-arm-eabi@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm-eabi@npm:1.3.5" dependencies: "@swc/wasm": 1.2.122 conditions: os=android & cpu=arm languageName: node linkType: hard -"@swc/core-android-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm64@npm:1.3.4" +"@swc/core-android-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-arm64@npm:1.3.4" +"@swc/core-darwin-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-arm64@npm:1.3.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-x64@npm:1.3.4" +"@swc/core-darwin-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-x64@npm:1.3.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-freebsd-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-freebsd-x64@npm:1.3.4" +"@swc/core-freebsd-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-freebsd-x64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.4" +"@swc/core-linux-arm-gnueabihf@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.4" +"@swc/core-linux-arm64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.4" +"@swc/core-linux-arm64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.4" +"@swc/core-linux-x64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-musl@npm:1.3.4" +"@swc/core-linux-x64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-musl@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.4" +"@swc/core-win32-arm64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.4" +"@swc/core-win32-ia32-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.4" +"@swc/core-win32-x64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.2.239": - version: 1.3.4 - resolution: "@swc/core@npm:1.3.4" + version: 1.3.5 + resolution: "@swc/core@npm:1.3.5" dependencies: - "@swc/core-android-arm-eabi": 1.3.4 - "@swc/core-android-arm64": 1.3.4 - "@swc/core-darwin-arm64": 1.3.4 - "@swc/core-darwin-x64": 1.3.4 - "@swc/core-freebsd-x64": 1.3.4 - "@swc/core-linux-arm-gnueabihf": 1.3.4 - "@swc/core-linux-arm64-gnu": 1.3.4 - "@swc/core-linux-arm64-musl": 1.3.4 - "@swc/core-linux-x64-gnu": 1.3.4 - "@swc/core-linux-x64-musl": 1.3.4 - "@swc/core-win32-arm64-msvc": 1.3.4 - "@swc/core-win32-ia32-msvc": 1.3.4 - "@swc/core-win32-x64-msvc": 1.3.4 + "@swc/core-android-arm-eabi": 1.3.5 + "@swc/core-android-arm64": 1.3.5 + "@swc/core-darwin-arm64": 1.3.5 + "@swc/core-darwin-x64": 1.3.5 + "@swc/core-freebsd-x64": 1.3.5 + "@swc/core-linux-arm-gnueabihf": 1.3.5 + "@swc/core-linux-arm64-gnu": 1.3.5 + "@swc/core-linux-arm64-musl": 1.3.5 + "@swc/core-linux-x64-gnu": 1.3.5 + "@swc/core-linux-x64-musl": 1.3.5 + "@swc/core-win32-arm64-msvc": 1.3.5 + "@swc/core-win32-ia32-msvc": 1.3.5 + "@swc/core-win32-x64-msvc": 1.3.5 dependenciesMeta: "@swc/core-android-arm-eabi": optional: true @@ -12664,7 +12664,7 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: 24b4ca4a096fea53056ed227a8aa09aa38cfe5eef344451397e66a2d183ded119872cf4fc8c671c0a6eb34985537cbf8d8a7e742b9e27dad0735426693d11dc8 + checksum: f0bcd0aa1d1a0b7d48ee5c416f8a384e19e4c0eed1e0be940d8893bf3981d98bf00f27d03bebf9cdedd34bf316d68c2db68e7bb7d1940d7b6062541e6e4cc738 languageName: node linkType: hard From c9198c9fce457788db93a3d809310c84025aea06 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sat, 8 Oct 2022 15:03:43 +0200 Subject: [PATCH 078/160] refactor(search-react): use mui table pagination component Signed-off-by: Camila Belo --- .changeset/search-days-pull.md | 19 +- .../app/src/components/search/SearchPage.tsx | 8 +- plugins/search-react/api-report.md | 78 ++++---- .../SearchPagination.stories.tsx} | 20 +- .../SearchPagination.test.tsx} | 110 ++++++++--- .../SearchPagination/SearchPagination.tsx | 178 ++++++++++++++++++ .../index.ts | 2 +- .../SearchResultLimiter.tsx | 150 --------------- plugins/search-react/src/components/index.ts | 2 +- 9 files changed, 331 insertions(+), 236 deletions(-) rename plugins/search-react/src/components/{SearchResultLimiter/SearchResultLimiter.stories.tsx => SearchPagination/SearchPagination.stories.tsx} (72%) rename plugins/search-react/src/components/{SearchResultLimiter/SearchResultLimiter.test.tsx => SearchPagination/SearchPagination.test.tsx} (58%) create mode 100644 plugins/search-react/src/components/SearchPagination/SearchPagination.tsx rename plugins/search-react/src/components/{SearchResultLimiter => SearchPagination}/index.ts (93%) delete mode 100644 plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx diff --git a/.changeset/search-days-pull.md b/.changeset/search-days-pull.md index 72fb9974a4..03000e4870 100644 --- a/.changeset/search-days-pull.md +++ b/.changeset/search-days-pull.md @@ -2,9 +2,7 @@ '@backstage/plugin-search-react': minor --- -A `` component was created for limiting the number of results shown per search page. -Use this new component to give users a combination of options to define how many search results they want to display per page. -The default options are 10, 25, 50, 100. +A `` component was created for limiting the number of results shown per search page. Use this new component to give users options to select how many search results they want to display per page. The default options are 10, 25, 50, 100. See examples below: @@ -16,13 +14,14 @@ import { Grid } from '@material-ui/core'; import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; import { SearchBarBase, - SearchResultLimiterBase, + SearchPaginationBase, SearchResultList, } from '@backstage/plugin-search-react'; const SearchPage = () => { const [term, setTerm] = useState(''); const [pageLimit, setPageLimit] = useState(25); + const [pageCursor, setPageCursor] = useState(); return ( @@ -33,9 +32,11 @@ const SearchPage = () => { - @@ -57,7 +58,7 @@ import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; import { SearchBar, SearchResult, - SearchResultLimiter, + SearchPagination, SearchResultListLayout, SearchContextProvider, DefaultResultListItem, @@ -73,7 +74,7 @@ const SearchPage = () => ( - + diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index b97ac99558..d324b683d8 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -35,7 +35,7 @@ import { SearchBar, SearchFilter, SearchResult, - SearchResultLimiter, + SearchPagination, SearchResultPager, useSearch, } from '@backstage/plugin-search-react'; @@ -56,10 +56,6 @@ const useStyles = makeStyles((theme: Theme) => ({ padding: theme.spacing(2), marginTop: theme.spacing(2), }, - limiter: { - width: '100%', - justifyContent: 'flex-end', - }, })); const SearchPage = () => { @@ -134,7 +130,7 @@ const SearchPage = () => { )} - + {({ results }) => ( diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 24b70240a0..57c1791d9a 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -211,6 +211,50 @@ export type SearchFilterWrapperProps = SearchFilterComponentProps & { debug?: boolean; }; +// @public +export const SearchPagination: (props: SearchPaginationProps) => JSX.Element; + +// @public +export const SearchPaginationBase: ( + props: SearchPaginationBaseProps, +) => JSX.Element; + +// @public +export type SearchPaginationBaseProps = { + className?: string; + pageCursor?: string; + onPageCursorChange?: (pageCursor: string) => void; + pageLimit?: number; + pageLimitLabel?: ReactNode; + pageLimitText?: SearchPaginationLimitText; + pageLimitOptions?: SearchPaginationLimitOption[]; + onPageLimitChange?: (value: number) => void; +}; + +// @public +export type SearchPaginationLimitOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchPaginationLimitOption< + Current, + [...Accumulator, Accumulator['length']] + >; + +// @public +export type SearchPaginationLimitText = (params: { + from: number; + to: number; + page: number; +}) => ReactNode; + +// @public +export type SearchPaginationProps = Omit< + SearchPaginationBaseProps, + 'pageLimit' | 'onPageLimitChange' | 'pageCursor' | 'onPageCursorChange' +>; + // @public export const SearchResult: (props: SearchResultProps) => JSX.Element; @@ -320,40 +364,6 @@ export const SearchResultGroupTextFilterField: ( export type SearchResultGroupTextFilterFieldProps = SearchResultGroupFilterFieldPropsWith<{}>; -// @public -export const SearchResultLimiter: ( - props: SearchResultLimiterProps, -) => JSX.Element; - -// @public -export const SearchResultLimiterBase: ( - props: SearchResultLimiterBaseProps, -) => JSX.Element; - -// @public -export type SearchResultLimiterBaseProps = { - id?: string; - className?: string; - label?: ReactNode; - options?: SearchResultLimiterOption[]; - value?: number; - onChange?: (value: number) => void; -}; - -// @public -export type SearchResultLimiterOption< - Current extends number = 101, - Accumulator extends number[] = [], -> = Accumulator['length'] extends Current - ? Accumulator[number] - : SearchResultLimiterOption; - -// @public -export type SearchResultLimiterProps = Omit< - SearchResultLimiterBaseProps, - 'value' | 'onChange' ->; - // @public export const SearchResultList: (props: SearchResultListProps) => JSX.Element; diff --git a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx similarity index 72% rename from plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.stories.tsx rename to plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx index f098f1ab68..eca668d165 100644 --- a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.stories.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -22,11 +22,11 @@ import { TestApiProvider } from '@backstage/test-utils'; import { searchApiRef, MockSearchApi } from '../../api'; import { SearchContextProvider } from '../../context'; -import { SearchResultLimiter } from './SearchResultLimiter'; +import { SearchPagination } from './SearchPagination'; export default { - title: 'Plugins/Search/SearchResultLimiter', - component: SearchResultLimiter, + title: 'Plugins/Search/SearchPagination', + component: SearchPagination, decorators: [ (Story: ComponentType<{}>) => ( @@ -43,13 +43,17 @@ export default { }; export const Default = () => { - return ; + return ; }; -export const CustomLabel = () => { - return ; +export const CustomPageLimitLabel = () => { + return ; }; -export const CustomOptions = () => { - return ; +export const CustomPageLimitText = () => { + return `${from}-${to}`} />; +}; + +export const CustomPageLimitOptions = () => { + return ; }; diff --git a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx similarity index 58% rename from plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.test.tsx rename to plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index a6f9f5b1e9..723a8a0031 100644 --- a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -23,11 +23,15 @@ import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; import { searchApiRef } from '../../api'; import { SearchContextProvider } from '../../context'; -import { SearchResultLimiter } from './SearchResultLimiter'; +import { SearchPagination } from './SearchPagination'; -const query = jest.fn().mockResolvedValue({ results: [] }); +const query = jest.fn().mockResolvedValue({ + results: [], + nextPageCursor: 'Mg==', + previousPageCursor: 'MA==', +}); -describe('SearchResultLimiter', () => { +describe('SearchPagination', () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -36,20 +40,23 @@ describe('SearchResultLimiter', () => { await renderWithEffects( - + , ); expect(screen.getByText('Results per page:')).toBeInTheDocument(); expect(screen.getByText('25')).toBeInTheDocument(); + expect(screen.getByText('1-25 of more than 25')).toBeInTheDocument(); + expect(screen.getByLabelText('Next page')).toBeEnabled(); + expect(screen.getByLabelText('Previous page')).toBeDisabled(); }); - it('Define default options', async () => { + it('Define default page limit options', async () => { await renderWithEffects( - + , ); @@ -64,11 +71,55 @@ describe('SearchResultLimiter', () => { expect(options[3]).toHaveTextContent('100'); }); + it('Accept custom page limit label', async () => { + const label = 'Page limit:'; + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText(label)).toBeInTheDocument(); + }); + + it('Accept custom page limit text', async () => { + await renderWithEffects( + + + `${from}-${to}`} /> + + , + ); + + expect(screen.getByText('1-25')).toBeInTheDocument(); + }); + + it('Accept custom page limit options', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(4); + expect(options[0]).toHaveTextContent('5'); + expect(options[1]).toHaveTextContent('10'); + expect(options[2]).toHaveTextContent('20'); + expect(options[3]).toHaveTextContent('25'); + }); + it('Set page limit in the context', async () => { await renderWithEffects( - + , ); @@ -84,35 +135,40 @@ describe('SearchResultLimiter', () => { ); }); - it('Accept custom label', async () => { - const label = 'Custom label'; + it('Set page cursor in the context', async () => { + const initialState = { + term: '', + types: [], + filters: {}, + pageCursor: 'MQ==', // page: 1 + }; + await renderWithEffects( - - + + , ); - expect(screen.getByText(label)).toBeInTheDocument(); - }); + await userEvent.click(screen.getByLabelText('Next page')); - it('Accept custom options', async () => { - await renderWithEffects( - - - - - , + expect(screen.getByText('51-75 of more than 75')).toBeInTheDocument(); + + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + pageCursor: 'Mg==', // page: 2 + }), ); - await userEvent.click(screen.getByText('25')); + await userEvent.click(screen.getByLabelText('Previous page')); - const options = screen.getAllByRole('option'); - expect(options).toHaveLength(4); - expect(options[0]).toHaveTextContent('5'); - expect(options[1]).toHaveTextContent('10'); - expect(options[2]).toHaveTextContent('20'); - expect(options[3]).toHaveTextContent('25'); + expect(screen.getByText('26-50 of more than 50')).toBeInTheDocument(); + + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + pageCursor: 'MQ==', // page: 1 + }), + ); }); }); diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx new file mode 100644 index 0000000000..c0d1c127c8 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -0,0 +1,178 @@ +/* + * 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 React, { + ReactNode, + ChangeEvent, + MouseEvent, + useCallback, + useMemo, +} from 'react'; +import { TablePagination } from '@material-ui/core'; +import { useSearch } from '../../context'; + +const encodePageCursor = (pageCursor: number): string => { + return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64'); +}; + +const decodePageCursor = (pageCursor?: string): number => { + if (!pageCursor) return 0; + return Number(Buffer.from(pageCursor, 'base64').toString('utf-8')); +}; + +/** + * A page limit option, this value must not be greater than 100. + * @public + */ +export type SearchPaginationLimitOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchPaginationLimitOption< + Current, + [...Accumulator, Accumulator['length']] + >; + +/** + * A page limit text, this function is called with a "\{ from, to, page \}" object. + * @public + */ +export type SearchPaginationLimitText = (params: { + from: number; + to: number; + page: number; +}) => ReactNode; + +/** + * Props for {@link SearchPaginationBase}. + * @public + */ +export type SearchPaginationBaseProps = { + /** + * The component class name. + */ + className?: string; + /** + * The cursor for the current page. + */ + pageCursor?: string; + /** + * Callback fired when the current page cursor is changed. + */ + onPageCursorChange?: (pageCursor: string) => void; + /** + * The limit of results per page. + * Set -1 to display all the results. + */ + pageLimit?: number; + /** + * Customize the results per page label. + */ + pageLimitLabel?: ReactNode; + /** + * Customize the results per page text. + */ + pageLimitText?: SearchPaginationLimitText; + /** + * Options for setting how many results show per page. + * If less than two options are available, no select field will be displayed. + * Use -1 for the value with a custom label to show all the results. + */ + pageLimitOptions?: SearchPaginationLimitOption[]; + /** + * Callback fired when the number of results per page is changed. + */ + onPageLimitChange?: (value: number) => void; +}; + +/** + * A component with controls for search results pagination. + * @param props - See {@link SearchPaginationBaseProps}. + * @public + */ +export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { + const { + pageCursor, + onPageCursorChange, + pageLimit: rowsPerPage = 25, + pageLimitLabel: labelRowsPerPage = 'Results per page:', + pageLimitText: labelDisplayedRows, + pageLimitOptions: rowsPerPageOptions, + onPageLimitChange, + ...rest + } = props; + + const page = useMemo(() => decodePageCursor(pageCursor), [pageCursor]); + + const handlePageChange = useCallback( + (_: MouseEvent | null, newValue: number) => { + onPageCursorChange?.(encodePageCursor(newValue)); + }, + [onPageCursorChange], + ); + + const handleRowsPerPageChange = useCallback( + (e: ChangeEvent) => { + const newValue = e.target.value; + onPageLimitChange?.(parseInt(newValue, 10)); + }, + [onPageLimitChange], + ); + + return ( + + ); +}; + +/** + * Props for {@link SearchPagination}. + * @public + */ +export type SearchPaginationProps = Omit< + SearchPaginationBaseProps, + 'pageLimit' | 'onPageLimitChange' | 'pageCursor' | 'onPageCursorChange' +>; + +/** + * A component for setting the search context page limit and cursor. + * @param props - See {@link SearchPaginationProps}. + * @public + */ +export const SearchPagination = (props: SearchPaginationProps) => { + const { pageLimit, setPageLimit, pageCursor, setPageCursor } = useSearch(); + + return ( + + ); +}; diff --git a/plugins/search-react/src/components/SearchResultLimiter/index.ts b/plugins/search-react/src/components/SearchPagination/index.ts similarity index 93% rename from plugins/search-react/src/components/SearchResultLimiter/index.ts rename to plugins/search-react/src/components/SearchPagination/index.ts index 411e48b5f5..74053f0e11 100644 --- a/plugins/search-react/src/components/SearchResultLimiter/index.ts +++ b/plugins/search-react/src/components/SearchPagination/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './SearchResultLimiter'; +export * from './SearchPagination'; diff --git a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx b/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx deleted file mode 100644 index 7f2d1ffc58..0000000000 --- a/plugins/search-react/src/components/SearchResultLimiter/SearchResultLimiter.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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 React, { ReactNode, ChangeEvent, useCallback } from 'react'; -import { - Box, - InputBase, - MenuItem, - Select, - Typography, - useTheme, -} from '@material-ui/core'; -import { useSearch } from '../../context'; - -/** - * A page limit option, this value must not be greater than 100. - * @public - */ -export type SearchResultLimiterOption< - Current extends number = 101, - Accumulator extends number[] = [], -> = Accumulator['length'] extends Current - ? Accumulator[number] - : SearchResultLimiterOption; - -/** - * Props for {@link SearchResultLimiterBase}. - * @public - */ -export type SearchResultLimiterBaseProps = { - id?: string; - className?: string; - /** - * A label for the combobox. - */ - label?: ReactNode; - /** - * The combobox labels, defaults to 10, 25, 50 and 100. - */ - options?: SearchResultLimiterOption[]; - /** - * Combobox selected option, defaults to 25; - */ - value?: number; - /** - * The callback handler called when the selected option changed. - */ - onChange?: (value: number) => void; -}; - -const DEFAULT_PAGE_LIMIT = 25; - -/** - * A component for selecting the number of results per page. - * @param props - See {@link SearchResultLimiterBaseProps}. - * @public - */ -export const SearchResultLimiterBase = ( - props: SearchResultLimiterBaseProps, -) => { - const { - id = 'search-result-limiter', - className, - label = 'Results per page:', - options = [10, 50, 100], - value = DEFAULT_PAGE_LIMIT, - onChange = () => {}, - } = props; - - const theme = useTheme(); - - const handleChange = useCallback( - (e: ChangeEvent<{ value: unknown }>) => { - const newValue = e.target.value; - if (typeof newValue === 'number') { - onChange(newValue); - } - }, - [onChange], - ); - - return ( - - - {label} - - - - ); -}; - -/** - * Props for {@link SearchResultLimiter}. - * @public - */ -export type SearchResultLimiterProps = Omit< - SearchResultLimiterBaseProps, - 'value' | 'onChange' ->; - -/** - * A component for setting the search context page limit. - * @param props - See {@link SearchResultLimiterProps}. - * @public - */ -export const SearchResultLimiter = (props: SearchResultLimiterProps) => { - const { pageLimit, setPageLimit } = useSearch(); - - return ( - - ); -}; diff --git a/plugins/search-react/src/components/index.ts b/plugins/search-react/src/components/index.ts index 524b0f9194..5f68905407 100644 --- a/plugins/search-react/src/components/index.ts +++ b/plugins/search-react/src/components/index.ts @@ -20,7 +20,7 @@ export * from './SearchAutocomplete'; export * from './SearchFilter'; export * from './SearchResult'; export * from './SearchResultPager'; -export * from './SearchResultLimiter'; +export * from './SearchPagination'; export * from './SearchResultList'; export * from './SearchResultGroup'; export * from './DefaultResultListItem'; From 90616bcaa6f9a1f5b34ae0e5f1a42dd62f3b32cd Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sat, 8 Oct 2022 15:04:49 +0200 Subject: [PATCH 079/160] refactor(search-react): add to create-app search page template Signed-off-by: Camila Belo --- .changeset/silent-mice-brake.md | 5 +++++ .../packages/app/src/components/search/SearchPage.tsx | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 .changeset/silent-mice-brake.md diff --git a/.changeset/silent-mice-brake.md b/.changeset/silent-mice-brake.md new file mode 100644 index 0000000000..c8ebb28f40 --- /dev/null +++ b/.changeset/silent-mice-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': minor +--- + +Add the new search pagination component to the search page template. diff --git a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx index 928b8201ea..9f11d0c80c 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx @@ -14,6 +14,7 @@ import { SearchBar, SearchFilter, SearchResult, + SearchPagination, useSearch, } from '@backstage/plugin-search-react'; import { @@ -109,6 +110,7 @@ const SearchPage = () => { + {({ results }) => ( From 46ee5188e91a8fe0bfd393fc493ae4123f34e72c Mon Sep 17 00:00:00 2001 From: andrmaz <60042277+andrmaz@users.noreply.github.com> Date: Sun, 9 Oct 2022 11:13:23 +0200 Subject: [PATCH 080/160] feat(curve): deduplicate curve factory type Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- packages/core-components/api-report.md | 6 +----- .../src/components/DependencyGraph/DependencyGraph.tsx | 3 +-- .../src/components/DependencyGraph/Edge.test.tsx | 4 ++-- .../src/components/DependencyGraph/Edge.tsx | 3 +-- .../src/components/DependencyGraph/types.ts | 10 ---------- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 6d6079bf8e..78574e71a3 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -203,9 +203,6 @@ export type CreateButtonProps = { title: string; } & Partial>; -// @public -type Curve = 'curveStepBefore' | 'curveMonotoneX'; - // @public (undocumented) export type CustomProviderClassKey = 'form' | 'button'; @@ -242,7 +239,7 @@ export interface DependencyGraphProps acyclicer?: 'greedy'; // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver align?: Alignment; - curve?: Curve; + curve?: 'curveStepBefore' | 'curveMonotoneX'; defs?: SVGDefsElement | SVGDefsElement[]; // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver direction?: Direction; @@ -273,7 +270,6 @@ declare namespace DependencyGraphTypes { DependencyNode, RenderNodeProps, RenderNodeFunction, - Curve, Direction, Alignment, Ranker, diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 3457a83d7d..5159f67d9e 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -30,7 +30,6 @@ import { RenderNodeFunction, RenderLabelFunction, LabelPosition, - Curve, } from './types'; import { Node } from './Node'; import { Edge, GraphEdge } from './Edge'; @@ -170,7 +169,7 @@ export interface DependencyGraphProps * * Default: 'curveMonotoneX' */ - curve?: Curve; + curve?: 'curveStepBefore' | 'curveMonotoneX'; } const WORKSPACE_ID = 'workspace'; diff --git a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx index ccb7bd7a9f..a44f25fbfc 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx @@ -17,11 +17,11 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Edge } from './Edge'; -import { Curve, RenderLabelProps } from './types'; +import { RenderLabelProps } from './types'; const fromNode = 'node'; const toNode = 'other-node'; -const curve: Curve = 'curveMonotoneX'; +const curve: 'curveStepBefore' | 'curveMonotoneX' = 'curveMonotoneX'; const edge = { points: [ diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index ea526827bf..aae737b9ea 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -24,7 +24,6 @@ import { RenderLabelFunction, DependencyEdge, LabelPosition, - Curve, } from './types'; import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; import { DefaultLabel } from './DefaultLabel'; @@ -71,7 +70,7 @@ export type EdgeComponentProps = { id: dagre.Edge, edge: DependencyEdge, ) => dagre.graphlib.Graph<{}>; - curve: Curve; + curve: 'curveStepBefore' | 'curveMonotoneX'; }; const renderDefault = (props: RenderLabelProps) => ( diff --git a/packages/core-components/src/components/DependencyGraph/types.ts b/packages/core-components/src/components/DependencyGraph/types.ts index 55226eb91d..4ae6057bd7 100644 --- a/packages/core-components/src/components/DependencyGraph/types.ts +++ b/packages/core-components/src/components/DependencyGraph/types.ts @@ -75,16 +75,6 @@ export type RenderNodeFunction = ( props: RenderNodeProps, ) => React.ReactNode; -/** - * Graph curve factory - * - * @public - * - * @see {@link @types/d3-shape/index.d.ts#curveMonotoneX} - * @see {@link @types/d3-shape/index.d.ts#curveStepBefore} - */ -export type Curve = 'curveStepBefore' | 'curveMonotoneX'; - /** * Graph direction * From 6a8a639265b4cc06dcce1f344f5eb5e14adcb866 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Sun, 9 Oct 2022 15:13:58 +0200 Subject: [PATCH 081/160] chore: remove unneeded changeset Signed-off-by: Ben Lambert --- .changeset/pink-pumas-double.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/pink-pumas-double.md diff --git a/.changeset/pink-pumas-double.md b/.changeset/pink-pumas-double.md deleted file mode 100644 index ca4d40967f..0000000000 --- a/.changeset/pink-pumas-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Attach groups hierarchy arrows to the right side of their parent boxes From b424a085dd366a6a46ba100410fcee8758e738f4 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Sun, 9 Oct 2022 15:14:40 +0200 Subject: [PATCH 082/160] chore: updating changeset text Signed-off-by: Ben Lambert --- .changeset/clever-clocks-drive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clever-clocks-drive.md b/.changeset/clever-clocks-drive.md index d91a8ee53f..0834133e36 100644 --- a/.changeset/clever-clocks-drive.md +++ b/.changeset/clever-clocks-drive.md @@ -3,4 +3,4 @@ '@backstage/plugin-catalog-graph': patch --- -Added select to toggle the curve factory of the dependency graph +Added `curve` prop to the `DependencyGraph` component to select the type of layout From 5b9b2e0e411961de83d5a6c989fb1e688780a781 Mon Sep 17 00:00:00 2001 From: andrmaz <60042277+andrmaz@users.noreply.github.com> Date: Sun, 9 Oct 2022 23:56:17 +0200 Subject: [PATCH 083/160] feat(curve): remove curve factory type Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- plugins/catalog-graph/api-report.md | 7 ++----- .../CatalogGraphPage/CatalogGraphPage.tsx | 3 +-- .../components/CatalogGraphPage/CurveFilter.tsx | 6 +++--- .../CatalogGraphPage/useCatalogGraphPage.ts | 14 ++++++++------ .../EntityRelationsGraph/EntityRelationsGraph.tsx | 4 ++-- .../src/components/EntityRelationsGraph/index.ts | 1 - .../src/components/EntityRelationsGraph/types.ts | 10 ---------- 7 files changed, 16 insertions(+), 29 deletions(-) diff --git a/plugins/catalog-graph/api-report.md b/plugins/catalog-graph/api-report.md index eac7351c33..e477fb13d9 100644 --- a/plugins/catalog-graph/api-report.md +++ b/plugins/catalog-graph/api-report.md @@ -30,7 +30,7 @@ export const CatalogGraphPage: (props: { mergeRelations?: boolean | undefined; direction?: Direction | undefined; showFilters?: boolean | undefined; - curve?: Curve | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; } | undefined; }) => JSX.Element; @@ -55,9 +55,6 @@ export const catalogGraphPlugin: BackstagePlugin< // @public export const catalogGraphRouteRef: RouteRef; -// @public -export type Curve = 'curveStepBefore' | 'curveMonotoneX'; - // @public export enum Direction { BOTTOM_TOP = 'BT', @@ -123,7 +120,7 @@ export const EntityRelationsGraph: (props: { renderLabel?: | DependencyGraphTypes.RenderLabelFunction | undefined; - curve?: Curve | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; }) => JSX.Element; // @public diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 38fcdad918..a6a6c2a711 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -35,7 +35,6 @@ import React, { MouseEvent, useCallback } from 'react'; import { useNavigate } from 'react-router'; import { ALL_RELATION_PAIRS, - Curve, Direction, EntityNode, EntityRelationsGraph, @@ -112,7 +111,7 @@ export const CatalogGraphPage = (props: { mergeRelations?: boolean; direction?: Direction; showFilters?: boolean; - curve?: Curve; + curve?: 'curveStepBefore' | 'curveMonotoneX'; }; }) => { const { relationPairs = ALL_RELATION_PAIRS, initialState } = props; diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx index b508d48172..6cda334cf1 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -16,8 +16,8 @@ import { Select } from '@backstage/core-components'; import { Box } from '@material-ui/core'; import React, { useCallback } from 'react'; -import { Curve } from '../EntityRelationsGraph'; +type Curve = 'curveStepBefore' | 'curveMonotoneX'; const CURVE_DISPLAY_NAMES: Record = { curveMonotoneX: 'Monotone X', curveStepBefore: 'Step Before', @@ -25,10 +25,10 @@ const CURVE_DISPLAY_NAMES: Record = { export type Props = { value: Curve; - onChange: (value: Curve) => void; + onChange: (value: 'curveStepBefore' | 'curveMonotoneX') => void; }; -const curves: Curve[] = ['curveMonotoneX', 'curveStepBefore']; +const curves: Array = ['curveMonotoneX', 'curveStepBefore']; export const CurveFilter = ({ value, onChange }: Props) => { const handleChange = useCallback(v => onChange(v as Curve), [onChange]); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts index 9126f5aef5..43da53c44a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts @@ -29,7 +29,7 @@ import { } from 'react'; import { useLocation } from 'react-router'; import usePrevious from 'react-use/lib/usePrevious'; -import { Direction, Curve } from '../EntityRelationsGraph'; +import { Direction } from '../EntityRelationsGraph'; export type CatalogGraphPageValue = { rootEntityNames: CompoundEntityRef[]; @@ -46,8 +46,10 @@ export type CatalogGraphPageValue = { setMergeRelations: Dispatch>; direction: Direction; setDirection: Dispatch>; - curve: Curve; - setCurve: Dispatch>; + curve: 'curveStepBefore' | 'curveMonotoneX'; + setCurve: Dispatch< + React.SetStateAction<'curveStepBefore' | 'curveMonotoneX'> + >; showFilters: boolean; toggleShowFilters: DispatchWithoutAction; }; @@ -64,7 +66,7 @@ export function useCatalogGraphPage({ mergeRelations?: boolean; direction?: Direction; showFilters?: boolean; - curve?: Curve; + curve?: 'curveStepBefore' | 'curveMonotoneX'; }; }): CatalogGraphPageValue { const location = useLocation(); @@ -80,7 +82,7 @@ export function useCatalogGraphPage({ mergeRelations?: string[] | string; direction?: string[] | Direction; showFilters?: string[] | string; - curve?: string[] | Curve; + curve?: string[] | 'curveStepBefore' | 'curveMonotoneX'; }, [location.search], ); @@ -126,7 +128,7 @@ export function useCatalogGraphPage({ ? query.direction : initialState?.direction ?? Direction.LEFT_RIGHT, ); - const [curve, setCurve] = useState(() => + const [curve, setCurve] = useState<'curveStepBefore' | 'curveMonotoneX'>(() => typeof query.curve === 'string' ? query.curve : initialState?.curve ?? 'curveMonotoneX', diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx index c6027e3e12..c0e2f610c1 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx @@ -29,7 +29,7 @@ import React, { MouseEvent, useEffect, useMemo } from 'react'; import { CustomLabel } from './CustomLabel'; import { CustomNode } from './CustomNode'; import { ALL_RELATION_PAIRS, RelationPairs } from './relations'; -import { Curve, Direction, EntityEdge, EntityNode } from './types'; +import { Direction, EntityEdge, EntityNode } from './types'; import { useEntityRelationNodesAndEdges } from './useEntityRelationNodesAndEdges'; const useStyles = makeStyles(theme => ({ @@ -82,7 +82,7 @@ export const EntityRelationsGraph = (props: { zoom?: 'enabled' | 'disabled' | 'enable-on-click'; renderNode?: DependencyGraphTypes.RenderNodeFunction; renderLabel?: DependencyGraphTypes.RenderLabelFunction; - curve?: Curve; + curve?: 'curveStepBefore' | 'curveMonotoneX'; }) => { const { rootEntityNames, diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/index.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/index.ts index 72125cb595..b86c8f05ae 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/index.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/index.ts @@ -22,5 +22,4 @@ export type { EntityEdge, EntityNodeData, EntityNode, - Curve, } from './types'; diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/types.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/types.ts index 68f81e5577..caf6c8c501 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/types.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/types.ts @@ -108,13 +108,3 @@ export enum Direction { */ RIGHT_LEFT = 'RL', } - -/** - * Graph curve factory - * - * @public - * - * @see {@link @types/d3-shape/index.d.ts#curveMonotoneX} - * @see {@link @types/d3-shape/index.d.ts#curveStepBefore} - */ -export type Curve = 'curveStepBefore' | 'curveMonotoneX'; From f7c2855d7661197965fa3cb10a1d1ed6f7696d24 Mon Sep 17 00:00:00 2001 From: Frida Jacobsson Date: Fri, 23 Sep 2022 09:22:18 +0200 Subject: [PATCH 084/160] Create "BazaarOverviewCard" that can be added to Backstage homepage. Change-Id: I6e2565360cb3f7af27f60a0336cd87a75b3018c3 Signed-off-by: Frida Jacobsson Signed-off-by: Frida Jacobsson --- .changeset/early-rocks-smell.md | 5 + .changeset/rude-tomatoes-behave.md | 5 + package.json | 1 + plugins/bazaar-backend/package.json | 1 + .../src/service/DatabaseHandler.ts | 18 ++- plugins/bazaar-backend/src/service/router.ts | 10 +- plugins/bazaar/README.md | 28 ++++ plugins/bazaar/api-report.md | 11 ++ plugins/bazaar/media/overviewCard.png | Bin 0 -> 91688 bytes plugins/bazaar/package.json | 1 + plugins/bazaar/src/api.ts | 19 ++- .../BazaarOverviewCard/BazaarOverviewCard.tsx | 141 ++++++++++++++++++ .../components/BazaarOverviewCard/index.ts | 18 +++ .../components/ProjectCard/ProjectCard.tsx | 21 ++- .../ProjectPreview/ProjectPreview.tsx | 42 ++++-- plugins/bazaar/src/index.ts | 2 + yarn.lock | 3 + 17 files changed, 292 insertions(+), 34 deletions(-) create mode 100644 .changeset/early-rocks-smell.md create mode 100644 .changeset/rude-tomatoes-behave.md create mode 100644 plugins/bazaar/media/overviewCard.png create mode 100644 plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx create mode 100644 plugins/bazaar/src/components/BazaarOverviewCard/index.ts diff --git a/.changeset/early-rocks-smell.md b/.changeset/early-rocks-smell.md new file mode 100644 index 0000000000..e35554f03a --- /dev/null +++ b/.changeset/early-rocks-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bazaar': patch +--- + +Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. diff --git a/.changeset/rude-tomatoes-behave.md b/.changeset/rude-tomatoes-behave.md new file mode 100644 index 0000000000..880b4dffd5 --- /dev/null +++ b/.changeset/rude-tomatoes-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bazaar-backend': patch +--- + +Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. diff --git a/package.json b/package.json index 40e2056e8c..1013b2a049 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "version": "1.7.0-next.1", "dependencies": { + "@backstage/errors": "workspace:^", "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.17.11", "@microsoft/api-extractor": "^7.23.0", diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 131959cbd6..31ae4c6cb5 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -25,6 +25,7 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.ts index 352866b4b5..8fbe985a10 100644 --- a/plugins/bazaar-backend/src/service/DatabaseHandler.ts +++ b/plugins/bazaar-backend/src/service/DatabaseHandler.ts @@ -175,14 +175,22 @@ export class DatabaseHandler { return await this.client('metadata').where({ id: id }).del(); } - async getProjects() { + async getProjects(limit?: number, order?: string) { const coalesce = this.client.raw( 'coalesce(count(members.item_id), 0) as members_count', ); - - return await this.client('metadata') + let get = this.client('metadata') .select([...this.columns, coalesce]) - .groupBy(this.columns) - .leftJoin('members', 'metadata.id', '=', 'members.item_id'); + .groupBy(this.columns); + if (limit) { + get = get.limit(limit); + } + if (order === 'latest') { + get = get.orderByRaw('id desc'); + } + if (order === 'random') { + get = get.orderByRaw('RANDOM()'); + } + return await get.leftJoin('members', 'metadata.id', '=', 'members.item_id'); } } diff --git a/plugins/bazaar-backend/src/service/router.ts b/plugins/bazaar-backend/src/service/router.ts index 359945f298..f902d0e8cc 100644 --- a/plugins/bazaar-backend/src/service/router.ts +++ b/plugins/bazaar-backend/src/service/router.ts @@ -92,8 +92,14 @@ export async function createRouter( response.json({ status: 'ok', data: data }); }); - router.get('/projects', async (_, response) => { - const data = await dbHandler.getProjects(); + router.get('/projects', async (request, response) => { + const limit = request.query.limit?.toString(); + const order = request.query.order?.toString(); + + const data = await dbHandler.getProjects( + limit ? parseInt(limit, 10) : undefined, + order, + ); response.json({ status: 'ok', data: data }); }); diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md index eeebf6eb19..6168b0c4ba 100644 --- a/plugins/bazaar/README.md +++ b/plugins/bazaar/README.md @@ -64,6 +64,30 @@ const overviewContent = ( {/* ...other entity-cards */} ``` +Add a **Bazaar overview card** to the homepage that displays either the latest projects or random projects. In `packages/app/src/components/home/HomePage.tsx` add: + +```diff ++ import { BazaarOverviewCard } from '@backstage/plugin-bazaar'; + +export const homePage = ( + + + + + ++ ++ ++ + ++ ++ ++ + + {/* ...other homepage items */} +``` + +Specify how many projects you want through the "limit" props. In the example above 4 cards is specified. + ## How does the Bazaar work? ### Layout @@ -72,6 +96,10 @@ The latest modified Bazaar projects are displayed in the Bazaar landing page, lo ![home](media/layout.png) +The "BazaarOverviewCard" can be displayed in Backstage homepage. + +![home](media/overviewCard.png) + ### Workflow To add a project to the bazaar, simply click on the `add-project` button and fill in the form. diff --git a/plugins/bazaar/api-report.md b/plugins/bazaar/api-report.md index c6afde73f2..2e6ebe6746 100644 --- a/plugins/bazaar/api-report.md +++ b/plugins/bazaar/api-report.md @@ -8,6 +8,17 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; +// @public (undocumented) +export const BazaarOverviewCard: ( + props: BazaarOverviewCardProps, +) => JSX.Element; + +// @public (undocumented) +export type BazaarOverviewCardProps = { + order: 'latest' | 'random'; + limit: number; +}; + // @public (undocumented) export const BazaarPage: () => JSX.Element; diff --git a/plugins/bazaar/media/overviewCard.png b/plugins/bazaar/media/overviewCard.png new file mode 100644 index 0000000000000000000000000000000000000000..676764f6e4a40990930796f49f94f848afb4d1b6 GIT binary patch literal 91688 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

R0rRcj1p8Jo9bK}5gJ5IPq$+Pu{`?J{ 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 id: 'bazaar', @@ -40,7 +41,7 @@ export interface BazaarApi { addMember(id: number, userId: string): Promise; - getProjects(): Promise; + getProjects(limit?: number, order?: string): Promise; deleteProject(id: number): Promise; } @@ -148,12 +149,20 @@ export class BazaarClient implements BazaarApi { ); } - async getProjects(): Promise { + async getProjects(limit?: number, order?: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); + const params = { + ...(limit ? { limit: limit.toString() } : {}), + ...(order ? { order } : {}), + }; + const query = new URLSearchParams(params); + const url = `projects?${query.toString()}`; - return await this.fetchApi - .fetch(`${baseUrl}/projects`) - .then(resp => resp.json()); + const data = await this.fetchApi.fetch(`${baseUrl}/${url}`); + if (!data.ok) { + throw await ResponseError.fromResponse(data); + } + return data.json(); } async deleteProject(id: number): Promise { diff --git a/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx new file mode 100644 index 0000000000..83358c0a05 --- /dev/null +++ b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx @@ -0,0 +1,141 @@ +/* + * 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 React, { useEffect, useState } from 'react'; +import { ProjectPreview } from '../ProjectPreview/ProjectPreview'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import type { BazaarProject } from '../../types'; +import { bazaarApiRef } from '../../api'; +import { fetchCatalogItems } from '../../util/fetchMethods'; +import { parseBazaarProject } from '../../util/parseMethods'; +import { ErrorPanel, InfoCard } from '@backstage/core-components'; +import { bazaarPlugin } from '../../plugin'; + +/** @public */ +export type BazaarOverviewCardProps = { + order: 'latest' | 'random'; + limit: number; +}; + +const getUnlinkedCatalogEntities = ( + bazaarProjects: BazaarProject[], + catalogEntities: Entity[], +) => { + const bazaarProjectRefs = bazaarProjects.map( + (project: BazaarProject) => project.entityRef, + ); + + return catalogEntities.filter((entity: Entity) => { + return !bazaarProjectRefs?.includes(stringifyEntityRef(entity)); + }); +}; + +/** @public */ +export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { + const { order, limit } = props; + const bazaarApi = useApi(bazaarApiRef); + const catalogApi = useApi(catalogApiRef); + const root = useRouteRef(bazaarPlugin.routes.root); + + const bazaarLink = { + title: 'Go to Bazaar', + link: root.toString(), + }; + + const [unlinkedCatalogEntities, setUnlinkedCatalogEntities] = + useState(); + + const [catalogEntities, fetchCatalogEntities] = useAsyncFn(async () => { + return await fetchCatalogItems(catalogApi); + }); + + const [bazaarProjects, fetchBazaarProjects] = useAsyncFn(async () => { + const response = await bazaarApi.getProjects(limit, order); + return response.data.map(parseBazaarProject) as BazaarProject[]; + }); + + const catalogEntityRefs = catalogEntities.value?.map((project: Entity) => + stringifyEntityRef(project), + ); + + useEffect(() => { + const filterBrokenLinks = () => { + if (catalogEntityRefs) { + bazaarProjects.value?.forEach(async (project: BazaarProject) => { + if (project.entityRef) { + if (!catalogEntityRefs?.includes(project.entityRef)) { + await bazaarApi.updateProject({ + ...project, + entityRef: null, + }); + } + } + }); + } + }; + filterBrokenLinks(); + }, [ + bazaarApi, + bazaarProjects.value, + catalogEntityRefs, + catalogEntities.value, + ]); + + useEffect(() => { + fetchCatalogEntities(); + fetchBazaarProjects(); + }, [fetchBazaarProjects, fetchCatalogEntities]); + + useEffect(() => { + const unlinkedCEntities = getUnlinkedCatalogEntities( + bazaarProjects.value || [], + catalogEntities.value || [], + ); + + if (unlinkedCEntities) { + setUnlinkedCatalogEntities(unlinkedCEntities); + } + }, [bazaarProjects, catalogEntities]); + + if (catalogEntities.error) { + return ; + } + + if (bazaarProjects.error) { + return ; + } + + return ( + + + + ); +}; diff --git a/plugins/bazaar/src/components/BazaarOverviewCard/index.ts b/plugins/bazaar/src/components/BazaarOverviewCard/index.ts new file mode 100644 index 0000000000..06ea623e1b --- /dev/null +++ b/plugins/bazaar/src/components/BazaarOverviewCard/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { BazaarOverviewCard } from './BazaarOverviewCard'; +export type { BazaarOverviewCardProps } from './BazaarOverviewCard'; diff --git a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx index 3aeba305a9..dace9b8185 100644 --- a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx +++ b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx @@ -30,6 +30,13 @@ import { DateTime } from 'luxon'; import { HomePageBazaarInfoCard } from '../HomePageBazaarInfoCard'; import { Entity } from '@backstage/catalog-model'; +type Props = { + project: BazaarProject; + fetchBazaarProjects: () => Promise; + catalogEntities: Entity[]; + fullHeight?: boolean; +}; + const useStyles = makeStyles({ statusTag: { display: 'inline-block', @@ -47,7 +54,7 @@ const useStyles = makeStyles({ float: 'right', }, content: { - height: '13rem', + overflow: 'scroll', }, header: { whiteSpace: 'nowrap', @@ -56,16 +63,11 @@ const useStyles = makeStyles({ }, }); -type Props = { - project: BazaarProject; - fetchBazaarProjects: () => Promise; - catalogEntities: Entity[]; -}; - export const ProjectCard = ({ project, fetchBazaarProjects, catalogEntities, + fullHeight, }: Props) => { const classes = useStyles(); const [openCard, setOpenCard] = useState(false); @@ -101,7 +103,10 @@ export const ProjectCard = ({ base: DateTime.now(), })}`} /> - + {Number(membersCount) === Number(1) diff --git a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx index 435d80408d..77c16ef306 100644 --- a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx +++ b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx @@ -25,6 +25,9 @@ type Props = { bazaarProjects: BazaarProject[]; fetchBazaarProjects: () => Promise; catalogEntities: Entity[]; + useTablePagination?: boolean; + fullHeight?: boolean; + fixedWidth?: boolean; }; const useStyles = makeStyles({ @@ -51,6 +54,9 @@ export const ProjectPreview = ({ bazaarProjects, fetchBazaarProjects, catalogEntities, + useTablePagination = true, + fullHeight = true, + fixedWidth = false, }: Props) => { const classes = useStyles(); const [page, setPage] = useState(1); @@ -80,31 +86,39 @@ export const ProjectPreview = ({ .slice((page - 1) * rows, rows * page) .map((bazaarProject: BazaarProject, i: number) => { return ( - + ); })} - = bazaarProjects.length, - }} - /> + {useTablePagination && ( + = bazaarProjects.length, + }} + /> + )} ); }; diff --git a/plugins/bazaar/src/index.ts b/plugins/bazaar/src/index.ts index 895469c031..0014c66b6d 100644 --- a/plugins/bazaar/src/index.ts +++ b/plugins/bazaar/src/index.ts @@ -15,5 +15,7 @@ */ export { bazaarPlugin, BazaarPage } from './plugin'; +export { BazaarOverviewCard } from './components/BazaarOverviewCard'; +export type { BazaarOverviewCardProps } from './components/BazaarOverviewCard'; export { EntityBazaarInfoCard } from './components/EntityBazaarInfoCard'; export { SortView } from './components/SortView'; diff --git a/yarn.lock b/yarn.lock index ac27eb8215..c710e002d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4349,6 +4349,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@types/express": ^4.17.6 express: ^4.17.1 @@ -4369,6 +4370,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@date-io/luxon": 1.x @@ -35073,6 +35075,7 @@ __metadata: "@backstage/cli": "workspace:*" "@backstage/codemods": "workspace:*" "@backstage/create-app": "workspace:*" + "@backstage/errors": "workspace:^" "@changesets/cli": ^2.14.0 "@manypkg/get-packages": ^1.1.3 "@microsoft/api-documenter": ^7.17.11 From 9bf19466683b5c0498570476339523a38ceba853 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 10 Oct 2022 09:11:15 +0200 Subject: [PATCH 085/160] Updated the documentation Signed-off-by: bnechyporenko --- .changeset/sixty-llamas-itch.md | 14 ----------- plugins/scaffolder/README.md | 42 ++++++++++++++++++++++++++++++++ plugins/scaffolder/src/plugin.ts | 15 ++---------- 3 files changed, 44 insertions(+), 27 deletions(-) delete mode 100644 .changeset/sixty-llamas-itch.md diff --git a/.changeset/sixty-llamas-itch.md b/.changeset/sixty-llamas-itch.md deleted file mode 100644 index 49e0bb3752..0000000000 --- a/.changeset/sixty-llamas-itch.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Make it possible to enable useLongPollingLogs in scaffolder plugin. - -For that you have to add to your app-config.yaml file this snippet: - -```yaml -scaffolder: - useLongPollingLogs: true -``` - -By default, the value is `false`. diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index b74c03ec34..8194976445 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -79,6 +79,48 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( ``` +### Troubleshooting + +In case if you encountered with the [issue of closing EventStream](https://github.com/backstage/backstage/issues/5535) +which auto-updates logs during the task execution, you can enable long polling. In order to you it, you have to create +in your codebase file in this location `packages/app/src/apis.ts` where you register `ScaffolderClient` with an extra +parameter `useLongPollingLogs: true`. By default, it is `false`. + +```typescript +import { + createApiFactory, + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; + +import { + scaffolderApiRef, + ScaffolderClient, +} from '@backstage/plugin-scaffolder'; + +createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + fetchApi, + useLongPollingLogs: true, + }), +}); +``` + +Api factory creates the API and adds it to a registry. Because this file is processed before the default scaffolder +api registry, it is added to the registry prior and overrides the default behavior. + ## Links - [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend) diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index f64525ac8e..f9b359d93a 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -20,7 +20,7 @@ import { EntityPicker } from './components/fields/EntityPicker/EntityPicker'; import { entityNamePickerValidation } from './components/fields/EntityNamePicker'; import { EntityNamePicker } from './components/fields/EntityNamePicker/EntityNamePicker'; import { OwnerPicker } from './components/fields/OwnerPicker/OwnerPicker'; -import { repoPickerValidation } from './components'; +import { repoPickerValidation } from './components/fields/RepoUrlPicker'; import { RepoUrlPicker } from './components/fields/RepoUrlPicker/RepoUrlPicker'; import { createScaffolderFieldExtension } from './extensions'; import { @@ -30,7 +30,6 @@ import { viewTechDocRouteRef, } from './routes'; import { - configApiRef, createApiFactory, createPlugin, createRoutableExtension, @@ -51,27 +50,17 @@ export const scaffolderPlugin = createPlugin({ createApiFactory({ api: scaffolderApiRef, deps: { - configApi: configApiRef, discoveryApi: discoveryApiRef, scmIntegrationsApi: scmIntegrationsApiRef, fetchApi: fetchApiRef, identityApi: identityApiRef, }, - factory: ({ - configApi, - discoveryApi, - scmIntegrationsApi, - fetchApi, - identityApi, - }) => + factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => new ScaffolderClient({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi, - useLongPollingLogs: - configApi.getOptionalBoolean('scaffolder.useLongPollingLogs') ?? - false, }), }), ], From 270ee83613e4a31599b6da14713a1d2ff60aee50 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 10 Oct 2022 12:08:16 +0200 Subject: [PATCH 086/160] refactor: change default page limit text Signed-off-by: Camila Belo --- .../SearchPagination/SearchPagination.stories.tsx | 8 ++++++-- .../SearchPagination/SearchPagination.test.tsx | 12 +++++++----- .../components/SearchPagination/SearchPagination.tsx | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx index eca668d165..c23fb1e863 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -47,11 +47,15 @@ export const Default = () => { }; export const CustomPageLimitLabel = () => { - return ; + return ; }; export const CustomPageLimitText = () => { - return `${from}-${to}`} />; + return ( + `${from}-${to} of more than ${to}`} + /> + ); }; export const CustomPageLimitOptions = () => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index 723a8a0031..6e4abcdbda 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -47,7 +47,7 @@ describe('SearchPagination', () => { expect(screen.getByText('Results per page:')).toBeInTheDocument(); expect(screen.getByText('25')).toBeInTheDocument(); - expect(screen.getByText('1-25 of more than 25')).toBeInTheDocument(); + expect(screen.getByText('1-25')).toBeInTheDocument(); expect(screen.getByLabelText('Next page')).toBeEnabled(); expect(screen.getByLabelText('Previous page')).toBeDisabled(); }); @@ -88,12 +88,14 @@ describe('SearchPagination', () => { await renderWithEffects( - `${from}-${to}`} /> + `${from}-${to} of more than ${to}`} + /> , ); - expect(screen.getByText('1-25')).toBeInTheDocument(); + expect(screen.getByText('1-25 of more than 25')).toBeInTheDocument(); }); it('Accept custom page limit options', async () => { @@ -153,7 +155,7 @@ describe('SearchPagination', () => { await userEvent.click(screen.getByLabelText('Next page')); - expect(screen.getByText('51-75 of more than 75')).toBeInTheDocument(); + expect(screen.getByText('51-75')).toBeInTheDocument(); expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -163,7 +165,7 @@ describe('SearchPagination', () => { await userEvent.click(screen.getByLabelText('Previous page')); - expect(screen.getByText('26-50 of more than 50')).toBeInTheDocument(); + expect(screen.getByText('26-50')).toBeInTheDocument(); expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx index c0d1c127c8..e5807832e9 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -110,7 +110,7 @@ export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { onPageCursorChange, pageLimit: rowsPerPage = 25, pageLimitLabel: labelRowsPerPage = 'Results per page:', - pageLimitText: labelDisplayedRows, + pageLimitText: labelDisplayedRows = ({ from, to }) => `${from}-${to}`, pageLimitOptions: rowsPerPageOptions, onPageLimitChange, ...rest From 77b9448291265464da86fc0802eceb71abbdbcf4 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 10 Oct 2022 12:12:26 +0200 Subject: [PATCH 087/160] chore: change create app changeset to patch Signed-off-by: Camila Belo --- .changeset/silent-mice-brake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/silent-mice-brake.md b/.changeset/silent-mice-brake.md index c8ebb28f40..fde02146c9 100644 --- a/.changeset/silent-mice-brake.md +++ b/.changeset/silent-mice-brake.md @@ -1,5 +1,5 @@ --- -'@backstage/create-app': minor +'@backstage/create-app': patch --- Add the new search pagination component to the search page template. From 0e14a26749e52d7122fa3bbf28969198182f312f Mon Sep 17 00:00:00 2001 From: Alex Crome Date: Mon, 10 Oct 2022 12:31:23 +0100 Subject: [PATCH 088/160] Updated Configuration definition docs Added sample for what `package.json` should look like when defining a `configSchema` Signed-off-by: Alex Crome --- docs/conf/defining.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/conf/defining.md b/docs/conf/defining.md index 6462f4f60a..29964dbcbc 100644 --- a/docs/conf/defining.md +++ b/docs/conf/defining.md @@ -23,6 +23,17 @@ Each package is searched for a schema at a single point of entry, a top-level inlined JSON schema, or a relative path to a schema file. Supported schema file formats are `.json` or `.d.ts`. +```jsonc title="package.json" +{ + // ... + "files": [ + // ... + "config.d.ts" + ], + "configSchema": "config.d.ts" +} +``` + > When defining a schema file, be sure to include the file in your > `package.json` > `"files"` field as well! From a64064f0d3e5e9ec19884d02a45c10bd5a0c0735 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 10 Oct 2022 15:31:43 +0200 Subject: [PATCH 089/160] add optional argument to createRouter to allow injection of CatalogClient to techdocs backend Signed-off-by: Morgan --- plugins/techdocs-backend/src/service/router.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index ca887efce2..d02d787282 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -58,6 +58,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; /** @@ -74,6 +75,7 @@ export type RecommendedDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; /** @@ -107,7 +109,7 @@ export async function createRouter( ): Promise { const router = Router(); const { publisher, config, logger, discovery } = options; - const catalogClient = new CatalogClient({ discoveryApi: discovery }); + const catalogClient = options.catalogClient ?? new CatalogClient({ discoveryApi: discovery }); const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = From 7ced1b4076bb06247030a162122c389d4558331f Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 10 Oct 2022 15:39:43 +0200 Subject: [PATCH 090/160] add changeset and api-report Signed-off-by: Morgan --- .changeset/techdocs-olive-worms-yell.md | 5 +++++ plugins/techdocs-backend/api-report.md | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 .changeset/techdocs-olive-worms-yell.md diff --git a/.changeset/techdocs-olive-worms-yell.md b/.changeset/techdocs-olive-worms-yell.md new file mode 100644 index 0000000000..cc72b9883b --- /dev/null +++ b/.changeset/techdocs-olive-worms-yell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': minor +--- + +Add optional catalogClient argument to createRouter parameters diff --git a/plugins/techdocs-backend/api-report.md b/plugins/techdocs-backend/api-report.md index 57e5e5170f..1f6aa1d66a 100644 --- a/plugins/techdocs-backend/api-report.md +++ b/plugins/techdocs-backend/api-report.md @@ -6,6 +6,7 @@ /// import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; @@ -79,6 +80,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; // @public @@ -90,6 +92,7 @@ export type RecommendedDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; // @public From e17332b2911880555b635981e7e59daf667a8ec0 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Mon, 10 Oct 2022 15:35:16 +0100 Subject: [PATCH 091/160] docs: fix toQuery method in custom rules permissions docs Signed-off-by: MT Lewis --- docs/permissions/custom-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 1408d732d3..bef1c0941f 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -30,7 +30,7 @@ export const isInSystemRule = createCatalogPermissionRule({ }, toQuery: (systemRef: string) => ({ key: 'relations.partOf', - value: systemRef, + values: [systemRef], }), }); From 8006f8a602bb862c818715b8596d627fec7f296f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 10 Oct 2022 18:02:33 +0200 Subject: [PATCH 092/160] Tag indexing logs with document type Signed-off-by: Eric Peterson --- .changeset/search-konstiga-fika-reglar.md | 9 +++++++++ .github/vale/Vocab/Backstage/accept.txt | 1 + .../adr-backend/src/search/DefaultAdrCollatorFactory.ts | 2 +- .../src/engines/ElasticSearchSearchEngineIndexer.ts | 2 +- plugins/search-backend-node/src/IndexBuilder.ts | 9 +++++---- .../src/collators/NewlineDelimitedJsonCollatorFactory.ts | 2 +- .../src/search/StackOverflowQuestionsCollatorFactory.ts | 2 +- .../src/search/DefaultTechDocsCollatorFactory.ts | 2 +- 8 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 .changeset/search-konstiga-fika-reglar.md diff --git a/.changeset/search-konstiga-fika-reglar.md b/.changeset/search-konstiga-fika-reglar.md new file mode 100644 index 0000000000..fb79a8ad6f --- /dev/null +++ b/.changeset/search-konstiga-fika-reglar.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-adr-backend': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-stack-overflow-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index ae94912f38..6175eb96b5 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -68,6 +68,7 @@ dataflow dayjs debounce Debounce +debuggability declaratively deduplicated deps diff --git a/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts b/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts index 21f0f1945f..78f6e0226e 100644 --- a/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts +++ b/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts @@ -113,7 +113,7 @@ export class DefaultAdrCollatorFactory implements DocumentCollatorFactory { this.catalogClient = options.catalogClient ?? new CatalogClient({ discoveryApi: options.discovery }); - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); this.parser = options.parser ?? createMadrParser(); this.reader = options.reader; this.scmIntegrations = ScmIntegrations.fromConfig(options.config); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index c81950ea41..e32c49559d 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -63,7 +63,7 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { constructor(options: ElasticSearchSearchEngineIndexerOptions) { super({ batchSize: options.batchSize }); - this.logger = options.logger; + this.logger = options.logger.child({ documentType: options.type }); this.startTimestamp = process.hrtime(); this.type = options.type; this.indexPrefix = options.indexPrefix; diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 0ba9ba0059..907519f289 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -112,13 +112,14 @@ export class IndexBuilder { }); Object.keys(this.collators).forEach(type => { + const taskLogger = this.logger.child({ documentType: type }); scheduler.addToSchedule({ id: `search_index_${type.replace('-', '_').toLocaleLowerCase('en-US')}`, scheduledRunner: this.collators[type].schedule, task: async () => { // Instantiate the collator. const collator = await this.collators[type].factory.getCollator(); - this.logger.info( + taskLogger.info( `Collating documents for ${type} via ${this.collators[type].factory.constructor.name}`, ); @@ -128,7 +129,7 @@ export class IndexBuilder { .concat(this.decorators[type] || []) .map(async factory => { const decorator = await factory.getDecorator(); - this.logger.info( + taskLogger.info( `Attached decorator via ${factory.constructor.name} to ${type} index pipeline.`, ); return decorator; @@ -144,13 +145,13 @@ export class IndexBuilder { [collator, ...decorators, indexer], (error: NodeJS.ErrnoException | null) => { if (error) { - this.logger.error( + taskLogger.error( `Collating documents for ${type} failed: ${error}`, ); reject(error); } else { // Signal index pipeline completion! - this.logger.info(`Collating documents for ${type} succeeded`); + taskLogger.info(`Collating documents for ${type} succeeded`); resolve(); } }, diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index fde6b9412a..9d922aace6 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -93,7 +93,7 @@ export class NewlineDelimitedJsonCollatorFactory options.type, options.searchPattern, options.reader, - options.logger, + options.logger.child({ documentType: options.type }), options.visibilityPermission, ); } diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts index 688f54f005..1aa0e5d300 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -76,7 +76,7 @@ export class StackOverflowQuestionsCollatorFactory this.apiKey = options.apiKey; this.maxPage = options.maxPage; this.requestParams = options.requestParams; - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); } static fromConfig( diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts index d458eeaba0..aaddb4fa51 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts @@ -90,7 +90,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.discovery = options.discovery; this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); this.catalogClient = options.catalogClient || new CatalogClient({ discoveryApi: options.discovery }); From 23f9199a0f30ad13340699fedd9875b38c0883bb Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Tue, 11 Oct 2022 01:48:29 +0200 Subject: [PATCH 093/160] chore(catalog/bitbucket): Deprecate catalog-backend-module-bitbucket Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. Signed-off-by: Patrick Jungermann --- .changeset/fresh-donuts-arrive.md | 8 ++++++++ plugins/catalog-backend-module-bitbucket/api-report.md | 4 ++-- plugins/catalog-backend-module-bitbucket/package.json | 1 + .../src/BitbucketDiscoveryProcessor.ts | 8 +++++++- .../src/lib/BitbucketRepositoryParser.ts | 1 + 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 .changeset/fresh-donuts-arrive.md diff --git a/.changeset/fresh-donuts-arrive.md b/.changeset/fresh-donuts-arrive.md new file mode 100644 index 0000000000..55a85533b8 --- /dev/null +++ b/.changeset/fresh-donuts-arrive.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket': patch +--- + +Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + +Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` +or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. diff --git a/plugins/catalog-backend-module-bitbucket/api-report.md b/plugins/catalog-backend-module-bitbucket/api-report.md index 34acd6e76d..053c6040a4 100644 --- a/plugins/catalog-backend-module-bitbucket/api-report.md +++ b/plugins/catalog-backend-module-bitbucket/api-report.md @@ -12,7 +12,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { ScmIntegrationRegistry } from '@backstage/integration'; -// @public (undocumented) +// @public @deprecated (undocumented) export class BitbucketDiscoveryProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry; @@ -37,7 +37,7 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// @public +// @public @deprecated export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; target: string; diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index ff0594154d..61657cfafd 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -2,6 +2,7 @@ "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", "version": "0.2.4-next.1", + "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts index 14bf90ee62..6924765483 100644 --- a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts @@ -41,7 +41,10 @@ import { const DEFAULT_BRANCH = 'master'; const DEFAULT_CATALOG_LOCATION = '/catalog-info.yaml'; -/** @public */ +/** + * @public + * @deprecated Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + */ export class BitbucketDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly parser: BitbucketRepositoryParser; @@ -70,6 +73,9 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { this.integrations = options.integrations; this.parser = options.parser || defaultRepositoryParser; this.logger = options.logger; + this.logger.warn( + 'Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead.', + ); } getProcessorName(): string { diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts index 57a162e1e4..97225e2579 100644 --- a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts +++ b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts @@ -26,6 +26,7 @@ import { Logger } from 'winston'; * results. * * @public + * @deprecated Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. */ export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; From 098f487e59d5d000e4a8752a7bcc1b0fa071d448 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 11 Oct 2022 09:15:14 +0200 Subject: [PATCH 094/160] refactor: add results total prop Signed-off-by: Camila Belo --- .changeset/search-days-pull.md | 8 +-- plugins/search-react/api-report.md | 16 +++--- .../SearchPagination.stories.tsx | 6 +-- .../SearchPagination.test.tsx | 18 +++++-- .../SearchPagination/SearchPagination.tsx | 52 ++++++++++++------- 5 files changed, 63 insertions(+), 37 deletions(-) diff --git a/.changeset/search-days-pull.md b/.changeset/search-days-pull.md index 03000e4870..45d59be514 100644 --- a/.changeset/search-days-pull.md +++ b/.changeset/search-days-pull.md @@ -33,10 +33,10 @@ const SearchPage = () => { diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 57c1791d9a..ef3e8b19bd 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -222,13 +222,14 @@ export const SearchPaginationBase: ( // @public export type SearchPaginationBaseProps = { className?: string; - pageCursor?: string; - onPageCursorChange?: (pageCursor: string) => void; - pageLimit?: number; - pageLimitLabel?: ReactNode; - pageLimitText?: SearchPaginationLimitText; - pageLimitOptions?: SearchPaginationLimitOption[]; - onPageLimitChange?: (value: number) => void; + total?: number; + cursor?: string; + onCursorChange?: (pageCursor: string) => void; + limit?: number; + limitLabel?: ReactNode; + limitText?: SearchPaginationLimitText; + limitOptions?: SearchPaginationLimitOption[]; + onLimitChange?: (value: number) => void; }; // @public @@ -247,6 +248,7 @@ export type SearchPaginationLimitText = (params: { from: number; to: number; page: number; + count: number; }) => ReactNode; // @public diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx index c23fb1e863..4e72c0fa25 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -47,17 +47,17 @@ export const Default = () => { }; export const CustomPageLimitLabel = () => { - return ; + return ; }; export const CustomPageLimitText = () => { return ( `${from}-${to} of more than ${to}`} + limitText={({ from, to }) => `${from}-${to} of more than ${to}`} /> ); }; export const CustomPageLimitOptions = () => { - return ; + return ; }; diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index 6e4abcdbda..d2ecc55d31 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -76,7 +76,7 @@ describe('SearchPagination', () => { await renderWithEffects( - + , ); @@ -84,12 +84,24 @@ describe('SearchPagination', () => { expect(screen.getByText(label)).toBeInTheDocument(); }); + it('Show the total in text', async () => { + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText('of 100')).toBeInTheDocument(); + }); + it('Accept custom page limit text', async () => { await renderWithEffects( `${from}-${to} of more than ${to}`} + limitText={({ from, to }) => `${from}-${to} of more than ${to}`} /> , @@ -102,7 +114,7 @@ describe('SearchPagination', () => { await renderWithEffects( - + , ); diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx index e5807832e9..34b6deaff5 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -48,13 +48,14 @@ export type SearchPaginationLimitOption< >; /** - * A page limit text, this function is called with a "\{ from, to, page \}" object. + * A page limit text, this function is called with a "\{ from, to, page, count \}" object. * @public */ export type SearchPaginationLimitText = (params: { from: number; to: number; page: number; + count: number; }) => ReactNode; /** @@ -66,37 +67,46 @@ export type SearchPaginationBaseProps = { * The component class name. */ className?: string; + /** + * The total number of results. + * For an unknown number of items, provide -1. + * Defaults to -1. + */ + total?: number; /** * The cursor for the current page. */ - pageCursor?: string; + cursor?: string; /** * Callback fired when the current page cursor is changed. */ - onPageCursorChange?: (pageCursor: string) => void; + onCursorChange?: (pageCursor: string) => void; /** * The limit of results per page. * Set -1 to display all the results. */ - pageLimit?: number; + limit?: number; /** * Customize the results per page label. + * Defaults to "Results per page:". */ - pageLimitLabel?: ReactNode; + limitLabel?: ReactNode; /** * Customize the results per page text. + * Defaults to "(\{ from, to, count \}) =\> count \> 0 ? `of $\{count\}` : `$\{from\}-$\{to\}`". */ - pageLimitText?: SearchPaginationLimitText; + limitText?: SearchPaginationLimitText; /** * Options for setting how many results show per page. * If less than two options are available, no select field will be displayed. * Use -1 for the value with a custom label to show all the results. + * Defaults to [10, 25, 50, 100]. */ - pageLimitOptions?: SearchPaginationLimitOption[]; + limitOptions?: SearchPaginationLimitOption[]; /** * Callback fired when the number of results per page is changed. */ - onPageLimitChange?: (value: number) => void; + onLimitChange?: (value: number) => void; }; /** @@ -106,13 +116,15 @@ export type SearchPaginationBaseProps = { */ export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { const { - pageCursor, - onPageCursorChange, - pageLimit: rowsPerPage = 25, - pageLimitLabel: labelRowsPerPage = 'Results per page:', - pageLimitText: labelDisplayedRows = ({ from, to }) => `${from}-${to}`, - pageLimitOptions: rowsPerPageOptions, - onPageLimitChange, + total: count = -1, + cursor: pageCursor, + onCursorChange: onPageCursorChange, + limit: rowsPerPage = 25, + limitLabel: labelRowsPerPage = 'Results per page:', + limitText: labelDisplayedRows = ({ from, to }) => + count > 0 ? `of ${count}` : `${from}-${to}`, + limitOptions: rowsPerPageOptions, + onLimitChange: onPageLimitChange, ...rest } = props; @@ -137,7 +149,7 @@ export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { { return ( ); }; From 847eb6fdcee58624b6e6ddee0348d2d5bbed3de2 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 11 Oct 2022 09:45:50 +0200 Subject: [PATCH 095/160] format and add backticks to changeset file Signed-off-by: Morgan --- .changeset/techdocs-olive-worms-yell.md | 2 +- plugins/techdocs-backend/src/service/router.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/techdocs-olive-worms-yell.md b/.changeset/techdocs-olive-worms-yell.md index cc72b9883b..66b205fc19 100644 --- a/.changeset/techdocs-olive-worms-yell.md +++ b/.changeset/techdocs-olive-worms-yell.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs-backend': minor --- -Add optional catalogClient argument to createRouter parameters +Add optional `catalogClient` argument to `createRoute` parameters diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index d02d787282..855d8b02f4 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -109,7 +109,8 @@ export async function createRouter( ): Promise { const router = Router(); const { publisher, config, logger, discovery } = options; - const catalogClient = options.catalogClient ?? new CatalogClient({ discoveryApi: discovery }); + const catalogClient = + options.catalogClient ?? new CatalogClient({ discoveryApi: discovery }); const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = From dff3ce6908e2d95a8c51c80a100ef255231fe8fa Mon Sep 17 00:00:00 2001 From: Axel Hecht Date: Tue, 11 Oct 2022 10:30:44 +0200 Subject: [PATCH 096/160] Add Feature Flags to Plugins navigation on microsite. Signed-off-by: Axel Hecht --- microsite/sidebars.json | 1 + 1 file changed, 1 insertion(+) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index a93e148317..bf6b036daf 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -226,6 +226,7 @@ "plugins/composability", "plugins/customization", "plugins/analytics", + "plugins/feature-flags", { "type": "subcategory", "label": "Backends and APIs", From 8749df3d028ceebb22cfec5cf31606ca970c7284 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 7 Oct 2022 13:56:34 +0200 Subject: [PATCH 097/160] feat(catalog/github): Add option to configure schedule via `app-config.yaml` Relates-to: PR #13859 Signed-off-by: Patrick Jungermann --- .changeset/famous-ligers-repair.md | 8 + docs/integrations/github/discovery.md | 35 +- .../api-report.md | 4 +- .../catalog-backend-module-github/config.d.ts | 10 + .../package.json | 3 +- .../providers/GitHubEntityProvider.test.ts | 305 +++++++++++------- .../src/providers/GitHubEntityProvider.ts | 29 +- .../GitHubEntityProviderConfig.test.ts | 38 ++- .../providers/GitHubEntityProviderConfig.ts | 11 + yarn.lock | 1 + 10 files changed, 309 insertions(+), 135 deletions(-) create mode 100644 .changeset/famous-ligers-repair.md diff --git a/.changeset/famous-ligers-repair.md b/.changeset/famous-ligers-repair.md new file mode 100644 index 0000000000..bc3780cba7 --- /dev/null +++ b/.changeset/famous-ligers-repair.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +`GitHubEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/github/discovery diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 9b60b41682..26cd660cde 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -39,10 +39,13 @@ And then add the entity provider to your catalog builder: + builder.addEntityProvider( + GitHubEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), + ); @@ -68,6 +71,11 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } customProviderId: organization: 'new-org' # string catalogPath: '/custom/path/catalog-info.yaml' # string @@ -111,26 +119,35 @@ This provider supports multiple organizations via unique provider IDs. Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. You can use wildcards - `*` or `**` - to search the path and/or the filename -- **filters** _(optional)_: - - **branch** _(optional)_: +- **`filters`** _(optional)_: + - **`branch`** _(optional)_: String used to filter results based on the branch name. - - **repository** _(optional)_: + - **`repository`** _(optional)_: Regular expression used to filter results based on the repository name. - - **topic** _(optional)_: + - **`topic`** _(optional)_: Both of the filters below may be used at the same time but the exclusion filter has the highest priority. In the example above, a repository with the `backstage-include` topic would still be excluded if it were also carrying the `experiments` topic. - - **include** _(optional)_: + - **`include`** _(optional)_: An array of strings used to filter in results based on their associated GitHub topics. If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested - - **exclude** _(optional)_: + - **`exclude`** _(optional)_: An array of strings used to filter out results based on their associated GitHub topics. If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. -- **organization**: +- **`host`** _(optional)_: + The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **`organization`**: Name of your organization account/workspace. If you want to add multiple organizations, you need to add one provider config each. -- **host** _(optional)_: - The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. ## GitHub API Rate Limits diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index c89a34c259..86fc94a532 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -16,6 +16,7 @@ import { GitHubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -55,7 +56,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GitHubEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index 1dcd49ea4e..8648490001 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { processors?: { @@ -103,6 +105,10 @@ export interface Config { exclude?: string[]; }; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -156,6 +162,10 @@ export interface Config { exclude?: string[]; }; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 1fb82a648a..86433d5c73 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -56,7 +56,8 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151" + "@types/lodash": "^4.14.151", + "luxon": "^3.0.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts index 63fb77288b..b2078172e9 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts @@ -15,7 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GitHubEntityProvider } from './GitHubEntityProvider'; @@ -381,132 +385,201 @@ describe('GitHubEntityProvider', () => { entities: expectedEntities, }); }); -}); -it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { - const config = new ConfigReader({ - catalog: { - providers: { - github: { - myProvider: { - organization: 'test-org', - catalogPath: 'custom/path/catalog-custom.yaml', - filters: { - branch: 'main', - repository: 'test-.*', - topic: { - exclude: ['backstage-exclude'], - include: ['backstage-include'], + it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + myProvider: { + organization: 'test-org', + catalogPath: 'custom/path/catalog-custom.yaml', + filters: { + branch: 'main', + repository: 'test-.*', + topic: { + exclude: ['backstage-exclude'], + include: ['backstage-include'], + }, }, }, }, }, }, - }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GitHubEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + const mockGetOrganizationRepositories = jest.spyOn( + helpers, + 'getOrganizationRepositories', + ); + + mockGetOrganizationRepositories.mockReturnValue( + Promise.resolve({ + repositories: [ + { + name: 'test-repo', + url: 'https://github.com/test-org/test-repo', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-include' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + }, + { + name: 'test-repo-2', + url: 'https://github.com/test-org/test-repo-2', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-include' }, + }, + { + topic: { name: 'backstage-exclude' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + }, + { + name: 'test-repo-3', + url: 'https://github.com/test-org/test-repo-3', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-exclude' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + }, + ], + }), + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${url}`, + 'backstage.io/managed-by-origin-location': `url:${url}`, + }, + name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', + }, + spec: { + presence: 'optional', + target: `${url}`, + type: 'url', + }, + }, + locationKey: 'github-provider:myProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); }); - const schedule = new PersistingTaskRunner(); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - const provider = GitHubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const mockGetOrganizationRepositories = jest.spyOn( - helpers, - 'getOrganizationRepositories', - ); - - mockGetOrganizationRepositories.mockReturnValue( - Promise.resolve({ - repositories: [ - { - name: 'test-repo', - url: 'https://github.com/test-org/test-repo', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-include' }, - }, - ], + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - { - name: 'test-repo-2', - url: 'https://github.com/test-org/test-repo-2', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-include' }, - }, - { - topic: { name: 'backstage-exclude' }, - }, - ], - }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - { - name: 'test-repo-3', - url: 'https://github.com/test-org/test-repo-3', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-exclude' }, - }, - ], - }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - ], - }), - ); - - await provider.connect(entityProviderConnection); - - const taskDef = schedule.getTasks()[0]; - expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); - await (taskDef.fn as () => Promise)(); - - const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', }, }, - locationKey: 'github-provider:myProvider', - }, - ]; + }); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - type: 'full', - entities: expectedEntities, + expect(() => + GitHubEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + }, + }, + }, + }); + + expect(() => + GitHubEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for github-provider:default', + ); + }); + + it('single simple provider config with schedule in config', async () => { + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + const providers = GitHubEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual('github-provider:default'); }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts index 3909b5a3c2..7d04b749dd 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubCredentialsProvider, @@ -60,9 +60,14 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GitHubEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const integrations = ScmIntegrations.fromConfig(config); return readProviderConfigs(config).map(providerConfig => { @@ -75,11 +80,21 @@ export class GitHubEntityProvider implements EntityProvider { ); } + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for github-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + return new GitHubEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ); }); } @@ -88,14 +103,14 @@ export class GitHubEntityProvider implements EntityProvider { config: GitHubEntityProviderConfig, integration: GitHubIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.config = config; this.integration = integration.config; this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); this.githubCredentialsProvider = SingleInstanceGithubCredentialsProvider.create(integration.config); } @@ -111,10 +126,10 @@ export class GitHubEntityProvider implements EntityProvider { return await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts index 66a5463bf5..e1b3a6cfbf 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readProviderConfigs } from './GitHubEntityProviderConfig'; describe('readProviderConfigs', () => { @@ -81,13 +82,22 @@ describe('readProviderConfigs', () => { organization: 'test-org1', host: 'ghe.internal.com', }, + providerWithSchedule: { + organization: 'test-org1', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, }, }, }, }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(6); + expect(providerConfigs).toHaveLength(7); expect(providerConfigs[0]).toEqual({ id: 'providerOrganizationOnly', organization: 'test-org1', @@ -101,6 +111,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[1]).toEqual({ id: 'providerCustomCatalogPath', @@ -115,6 +126,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[2]).toEqual({ id: 'providerWithRepositoryFilter', @@ -129,6 +141,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[3]).toEqual({ id: 'providerWithBranchFilter', @@ -143,6 +156,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[4]).toEqual({ id: 'providerWithTopicFilter', @@ -157,6 +171,7 @@ describe('readProviderConfigs', () => { exclude: ['backstage-exclude'], }, }, + schedule: undefined, }); expect(providerConfigs[5]).toEqual({ id: 'providerWithHost', @@ -171,6 +186,27 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, + }); + expect(providerConfigs[6]).toEqual({ + id: 'providerWithSchedule', + organization: 'test-org1', + catalogPath: '/catalog-info.yaml', + host: 'github.com', + filters: { + repository: undefined, + branch: undefined, + topic: { + include: undefined, + exclude: undefined, + }, + }, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, }); }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts index d40b206751..6785d18984 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -29,6 +33,7 @@ export type GitHubEntityProviderConfig = { branch?: string; topic?: GithubTopicFilters; }; + schedule?: TaskScheduleDefinition; }; export type GithubTopicFilters = { @@ -73,6 +78,10 @@ function readProviderConfig( 'filters.topic.exclude', ); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, catalogPath, @@ -88,8 +97,10 @@ function readProviderConfig( exclude: topicFilterExclude, }, }, + schedule, }; } + /** * Compiles a RegExp while enforcing the pattern to contain * the start-of-line and end-of-line anchors. diff --git a/yarn.lock b/yarn.lock index 8be2e5c8a6..06bac843bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4590,6 +4590,7 @@ __metadata: "@types/lodash": ^4.14.151 git-url-parse: ^13.0.0 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 From a72ae031901ee2a49a1c40ec01ffb9108498b2f1 Mon Sep 17 00:00:00 2001 From: Harrison Hogg <7130591+HHogg@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:18:49 +0100 Subject: [PATCH 098/160] Update .changeset/quick-meals-talk.md Co-authored-by: MT Lewis Signed-off-by: Harrison Hogg <7130591+HHogg@users.noreply.github.com> --- .changeset/quick-meals-talk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-meals-talk.md b/.changeset/quick-meals-talk.md index bde70d25fd..b9ce0189f0 100644 --- a/.changeset/quick-meals-talk.md +++ b/.changeset/quick-meals-talk.md @@ -2,7 +2,7 @@ '@backstage/plugin-playlist-backend': minor --- -**BREAKING** Due to the changes made in the Permission framework. The playlist backends permission rules have change to reflect this. +**BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. As an example the `playlistConditions.isOwner` API has changed from From 9db6099633e1fe73770079789f90b2ca240fb928 Mon Sep 17 00:00:00 2001 From: Harrison Hogg <7130591+HHogg@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:19:01 +0100 Subject: [PATCH 099/160] Update .changeset/quick-meals-talk.md Co-authored-by: MT Lewis Signed-off-by: Harrison Hogg <7130591+HHogg@users.noreply.github.com> --- .changeset/quick-meals-talk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-meals-talk.md b/.changeset/quick-meals-talk.md index b9ce0189f0..b27034d684 100644 --- a/.changeset/quick-meals-talk.md +++ b/.changeset/quick-meals-talk.md @@ -4,7 +4,7 @@ **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. -As an example the `playlistConditions.isOwner` API has changed from +For example, the `playlistConditions.isOwner` API has changed from: ```ts playlistConditions.isOwner(['user:default/me', 'group:default/owner']); From ddf3986add991869fb67cba9ec2f903f0558665f Mon Sep 17 00:00:00 2001 From: Harrison Hogg <7130591+HHogg@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:19:11 +0100 Subject: [PATCH 100/160] Update .changeset/quick-meals-talk.md Co-authored-by: MT Lewis Signed-off-by: Harrison Hogg <7130591+HHogg@users.noreply.github.com> --- .changeset/quick-meals-talk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-meals-talk.md b/.changeset/quick-meals-talk.md index b27034d684..5834928e77 100644 --- a/.changeset/quick-meals-talk.md +++ b/.changeset/quick-meals-talk.md @@ -10,7 +10,7 @@ For example, the `playlistConditions.isOwner` API has changed from: playlistConditions.isOwner(['user:default/me', 'group:default/owner']); ``` -to the new API +to: ```ts playlistConditions.isOwner({ From a9771a69f6447e00aaa223241b839cdb52c7732b Mon Sep 17 00:00:00 2001 From: Mengnan Gong Date: Tue, 11 Oct 2022 17:30:22 +0800 Subject: [PATCH 101/160] Fix the example in the switching to postgres tutorial The `ssl` config is part of the `connection` config. It's misplaced under the `knexConfig.pool`, which might lead to confusion. This PR fixes it. Signed-off-by: Mengnan Gong --- docs/tutorials/switching-sqlite-postgres.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index c0c35999c6..bd067ee388 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -72,6 +72,12 @@ backend: + port: ${POSTGRES_PORT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt + # Refer to Tarn docs for default values on PostgreSQL pool configuration - https://github.com/Vincit/tarn.js + knexConfig: + pool: @@ -79,12 +85,6 @@ backend: + max: 12 + acquireTimeoutMillis: 60000 + idleTimeoutMillis: 60000 -+ # https://node-postgres.com/features/ssl -+ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable -+ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) -+ # ssl: -+ # ca: # if you have a CA file and want to verify it you can uncomment this section -+ # $file: /ca/server.crt ``` ### Using a single database From e10411cebc3ed25c788415c5441d8399918081c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 11 Oct 2022 10:15:11 +0100 Subject: [PATCH 102/160] tweak wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/scaffolder/README.md | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index 8194976445..83fa01e6e5 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -81,10 +81,10 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( ### Troubleshooting -In case if you encountered with the [issue of closing EventStream](https://github.com/backstage/backstage/issues/5535) -which auto-updates logs during the task execution, you can enable long polling. In order to you it, you have to create -in your codebase file in this location `packages/app/src/apis.ts` where you register `ScaffolderClient` with an extra -parameter `useLongPollingLogs: true`. By default, it is `false`. +If you encounter the [issue of closing EventStream](https://github.com/backstage/backstage/issues/5535) +which auto-updates logs during task execution, you can enable long polling. To do so, +update your `packages/app/src/apis.ts` file to register a `ScaffolderClient` with the +`useLongPollingLogs` set to `true`. By default, it is `false`. ```typescript import { @@ -93,33 +93,33 @@ import { fetchApiRef, identityApiRef, } from '@backstage/core-plugin-api'; - import { scaffolderApiRef, ScaffolderClient, } from '@backstage/plugin-scaffolder'; -createApiFactory({ - api: scaffolderApiRef, - deps: { - discoveryApi: discoveryApiRef, - identityApi: identityApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => - new ScaffolderClient({ - discoveryApi, - identityApi, - scmIntegrationsApi, - fetchApi, - useLongPollingLogs: true, - }), -}); +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + fetchApi, + useLongPollingLogs: true, + }), + }), + // ... other factories ``` -Api factory creates the API and adds it to a registry. Because this file is processed before the default scaffolder -api registry, it is added to the registry prior and overrides the default behavior. +This replaces the default implementation of the `scaffolderApiRef`. ## Links From 6acaedb962fce171e2528b61df18acb18cd4778d Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 18 Aug 2022 14:51:15 +0200 Subject: [PATCH 103/160] chore: things are working pretty nicely now with the later version and the next version of scaffolder Signed-off-by: blam --- package.json | 3 ++- plugins/scaffolder/package.json | 6 ++++-- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 6 ++++-- .../src/next/TemplateWizardPage/Stepper/Stepper.tsx | 2 ++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1013b2a049..267fccc029 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "workspaces": { "packages": [ "packages/*", - "plugins/*" + "plugins/*", + "../react-jsonschema-form/packages/*" ] }, "resolutions": { diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9b81d6593a..1d9c6013b7 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -54,8 +54,10 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@react-hookz/web": "^15.0.0", - "@rjsf/core": "^3.2.1", - "@rjsf/material-ui": "^3.2.1", + "@rjsf/core": "file:../../../react-jsonschema-form/packages/core", + "@rjsf/material-ui": "file:../../../react-jsonschema-form/packages/material-ui", + "@rjsf/utils": "file:../../../react-jsonschema-form/packages/utils", + "@rjsf/validator-ajv6": "file:../../../react-jsonschema-form/packages/validator-ajv6", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 8c71df67b6..60d242e0a1 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -30,7 +30,8 @@ import { featureFlagsApiRef, } from '@backstage/core-plugin-api'; import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; -import { Theme as MuiTheme } from '@rjsf/material-ui'; +import { Theme } from '@rjsf/material-ui'; +import validator from '@rjsf/validator-ajv6'; import React, { useState } from 'react'; import { transformSchemaToProps } from './schema'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; @@ -38,7 +39,6 @@ import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; -const Form = withTheme(MuiTheme); type Step = { schema: JsonObject; title: string; @@ -123,6 +123,7 @@ export const MultistepJsonForm = (props: Props) => { finishButtonLabel, layouts, } = props; + const Form = withTheme(Theme); const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -206,6 +207,7 @@ export const MultistepJsonForm = (props: Props) => {

({ backButton: { @@ -124,6 +125,7 @@ export const Stepper = (props: StepperProps) => {
{activeStep < steps.length ? ( Date: Tue, 30 Aug 2022 13:43:40 +0200 Subject: [PATCH 104/160] chore: fixing dependencies for the form Signed-off-by: blam --- plugins/scaffolder/package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 1d9c6013b7..b778f9d31f 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -54,10 +54,9 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@react-hookz/web": "^15.0.0", - "@rjsf/core": "file:../../../react-jsonschema-form/packages/core", - "@rjsf/material-ui": "file:../../../react-jsonschema-form/packages/material-ui", - "@rjsf/utils": "file:../../../react-jsonschema-form/packages/utils", - "@rjsf/validator-ajv6": "file:../../../react-jsonschema-form/packages/validator-ajv6", + "@rjsf/core": "^5.0.0-beta.2", + "@rjsf/material-ui": "^5.0.0-beta.2", + "@rjsf/validator-ajv6": "^5.0.0-beta.2", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", From 618312500827c52eda17fc6b96a6e9f000b204a7 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 Aug 2022 14:05:36 +0200 Subject: [PATCH 105/160] chore: fixing up typescript Signed-off-by: blam --- package.json | 3 +-- packages/app/package.json | 1 - .../src/components/scaffolder/customScaffolderExtensions.tsx | 2 +- plugins/scaffolder/package.json | 1 + .../MultistepJsonForm/FieldOverrides/DescriptionField.tsx | 2 +- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 5 +++-- .../src/components/TemplatePage/createValidator.test.ts | 2 +- .../src/components/TemplatePage/createValidator.ts | 2 +- .../components/fields/EntityNamePicker/validation.test.ts | 2 +- .../src/components/fields/EntityNamePicker/validation.ts | 2 +- .../src/components/fields/EntityPicker/EntityPicker.test.tsx | 2 +- .../src/components/fields/OwnerPicker/OwnerPicker.test.tsx | 2 +- .../components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx | 5 ++++- .../src/components/fields/RepoUrlPicker/validation.test.ts | 2 +- .../src/components/fields/RepoUrlPicker/validation.ts | 2 +- plugins/scaffolder/src/extensions/types.ts | 2 +- .../src/next/TemplateWizardPage/Stepper/Stepper.tsx | 3 ++- .../next/TemplateWizardPage/Stepper/createAsyncValidators.ts | 2 +- .../scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts | 2 +- .../src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts | 2 +- 20 files changed, 25 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 267fccc029..1013b2a049 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,7 @@ "workspaces": { "packages": [ "packages/*", - "plugins/*", - "../react-jsonschema-form/packages/*" + "plugins/*" ] }, "resolutions": { diff --git a/packages/app/package.json b/packages/app/package.json index 76e099bace..fa8bb5c5ee 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -83,7 +83,6 @@ }, "devDependencies": { "@backstage/test-utils": "workspace:^", - "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx index 2a4dedf080..3f6bd33d89 100644 --- a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx +++ b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import type { FieldValidation } from '@rjsf/core'; +import type { FieldValidation } from '@rjsf/utils'; import { createScaffolderFieldExtension, FieldExtensionComponentProps, diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index b778f9d31f..cf28227e8e 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -56,6 +56,7 @@ "@react-hookz/web": "^15.0.0", "@rjsf/core": "^5.0.0-beta.2", "@rjsf/material-ui": "^5.0.0-beta.2", + "@rjsf/utils": "^5.0.0-beta.2", "@rjsf/validator-ajv6": "^5.0.0-beta.2", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx index 3c592860ef..eded38beee 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { MarkdownContent } from '@backstage/core-components'; -import { FieldProps } from '@rjsf/core'; +import { FieldProps } from '@rjsf/utils'; export const DescriptionField = ({ description }: FieldProps) => description && ; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 60d242e0a1..064299f667 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -29,7 +29,8 @@ import { useApi, featureFlagsApiRef, } from '@backstage/core-plugin-api'; -import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; +import { UiSchema } from '@rjsf/utils'; +import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; import { Theme } from '@rjsf/material-ui'; import validator from '@rjsf/validator-ajv6'; import React, { useState } from 'react'; @@ -215,7 +216,7 @@ export const MultistepJsonForm = (props: Props) => { formData={formData} formContext={{ formData }} onChange={onChange} - onSubmit={e => { + onSubmit={(e: any) => { if (e.errors.length === 0) handleNext(); }} {...formProps} diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts index f549c9e7f2..c17b72148e 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts @@ -17,7 +17,7 @@ import { createValidator } from './createValidator'; import { CustomFieldValidator } from '../../extensions'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { FormValidation } from '@rjsf/core'; +import { FormValidation } from '@rjsf/utils'; describe('createValidator', () => { const validators: Record> = diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts index 9cd1489a02..2769b899df 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts @@ -15,7 +15,7 @@ */ import { CustomFieldValidator } from '../../extensions'; -import { FormValidation } from '@rjsf/core'; +import { FormValidation } from '@rjsf/utils'; import { JsonObject, JsonValue } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts index 290650387e..7201c9c4b3 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; import { entityNamePickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts index 7a40a460ba..1533b0cdc3 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; export const entityNamePickerValidation = ( diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 942eef9d82..f4eefb26d5 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { FieldProps } from '@rjsf/core'; +import { FieldProps } from '@rjsf/utils'; import { fireEvent } from '@testing-library/react'; import React from 'react'; import { EntityPicker } from './EntityPicker'; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx index b78d3add9e..68c9723974 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { FieldProps } from '@rjsf/core'; +import { FieldProps } from '@rjsf/utils'; import React from 'react'; import { OwnerPicker } from './OwnerPicker'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 7fac9f7840..be7c05155c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -15,7 +15,8 @@ */ import React, { useContext } from 'react'; import { RepoUrlPicker } from './RepoUrlPicker'; -import Form from '@rjsf/core'; +import { withTheme } from '@rjsf/core'; +import { Theme } from '@rjsf/material-ui'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { scmIntegrationsApiRef, @@ -50,6 +51,8 @@ describe('RepoUrlPicker', () => { getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }), }; + const Form = withTheme(Theme); + describe('happy path rendering', () => { it('should render the repo url picker with minimal props', async () => { const onSubmit = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts index 45e44b4072..b9c481957c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts @@ -15,7 +15,7 @@ */ import { repoPickerValidation } from './validation'; -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/core-app-api'; import { ApiHolder } from '@backstage/core-plugin-api'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index 99847f9e3b..55ba92abdc 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { ApiHolder } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 9b42ba478b..c3d99a6b37 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ApiHolder } from '@backstage/core-plugin-api'; -import { FieldValidation, FieldProps } from '@rjsf/core'; +import { FieldValidation, FieldProps } from '@rjsf/utils'; /** * Field validation type for Custom Field Extensions. diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index 8ae26ebf4a..ccdf32542a 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -22,7 +22,8 @@ import { Button, makeStyles, } from '@material-ui/core'; -import { FieldValidation, withTheme } from '@rjsf/core'; +import { withTheme } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useMemo, useState } from 'react'; import { FieldExtensionOptions } from '../../../extensions'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts index d89f0d4e5f..60eed0f364 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { JsonObject } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; import { CustomFieldValidator } from '../../../extensions'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts index e2d5e89932..355bfde45d 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { FieldValidation, UiSchema } from '@rjsf/core'; +import { FieldValidation, UiSchema } from '@rjsf/utils'; function isObject(value: unknown): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts index d0d8590a7b..6aad6baa96 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts @@ -15,7 +15,7 @@ */ import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; -import { UiSchema } from '@rjsf/core'; +import { UiSchema } from '@rjsf/utils'; import { TemplateParameterSchema } from '../../../types'; import { extractSchemaFromStep } from './schema'; From b9f71b15af19c8ae634d81edffb237a40ebae6fc Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 12 Sep 2022 11:09:45 +0200 Subject: [PATCH 106/160] chore: just the error validation left I think for the `next` scaffolder Signed-off-by: blam --- .../scaffolder/customScaffolderExtensions.tsx | 2 +- .../TemplatePage/createValidator.test.ts | 2 +- .../TemplatePage/createValidator.ts | 2 +- .../EntityNamePicker/EntityNamePicker.tsx | 2 +- .../fields/EntityPicker/EntityPicker.test.tsx | 6 +- .../fields/EntityPicker/EntityPicker.tsx | 13 +- .../EntityTagsPicker/EntityTagsPicker.tsx | 6 +- .../OwnedEntityPicker/OwnedEntityPicker.tsx | 8 +- .../fields/OwnerPicker/OwnerPicker.test.tsx | 2 +- .../fields/OwnerPicker/OwnerPicker.tsx | 8 +- .../RepoUrlPicker/RepoUrlPicker.test.tsx | 7 +- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 5 +- .../fields/RepoUrlPicker/validation.ts | 2 +- plugins/scaffolder/src/extensions/index.tsx | 3 +- plugins/scaffolder/src/extensions/types.ts | 18 ++- yarn.lock | 115 ++++++++++-------- 16 files changed, 114 insertions(+), 87 deletions(-) diff --git a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx index 3f6bd33d89..3aadd69c05 100644 --- a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx +++ b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx @@ -30,7 +30,7 @@ const TextValuePicker = (props: FieldExtensionComponentProps) => { schema: { title, description }, rawErrors, formData, - uiSchema: { 'ui:autofocus': autoFocus }, + uiSchema: { 'ui:autofocus': autoFocus } = {}, idSchema, placeholder, } = props; diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts index c17b72148e..eea5efc296 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts @@ -68,6 +68,6 @@ describe('createValidator', () => { /* THEN */ expect(result).not.toBeNull(); - expect(result.p1.addError).toHaveBeenCalledTimes(1); + expect(result.p1?.addError).toHaveBeenCalledTimes(1); }); }); diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts index 2769b899df..d1c55009a9 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts @@ -62,7 +62,7 @@ export const createValidator = ( if (fieldName && typeof validators[fieldName] === 'function') { validators[fieldName]!( propData as JsonValue, - propValidation, + propValidation as FormValidation, context, ); } diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 802135a863..2b21141edb 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -29,7 +29,7 @@ export const EntityNamePicker = ( schema: { title = 'Name', description = 'Unique name of the component' }, rawErrors, formData, - uiSchema: { 'ui:autofocus': autoFocus }, + uiSchema: { 'ui:autofocus': autoFocus } = {}, idSchema, placeholder, } = props; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index f4eefb26d5..ffc447577a 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -39,7 +39,7 @@ describe('', () => { const rawErrors: string[] = []; const formData = undefined; - let props: FieldProps; + let props: FieldProps; const catalogApi: jest.Mocked = { getLocationById: jest.fn(), @@ -76,7 +76,7 @@ describe('', () => { uiSchema, rawErrors, formData, - } as unknown as FieldProps; + } as any; catalogApi.getEntities.mockResolvedValue({ items: entities }); }); @@ -117,7 +117,7 @@ describe('', () => { uiSchema, rawErrors, formData, - } as unknown as FieldProps; + } as any; catalogApi.getEntities.mockResolvedValue({ items: entities }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index e7883a4721..a040320d79 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -45,7 +45,10 @@ export interface EntityPickerUiOptions { * @public */ export const EntityPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + string | undefined, + EntityPickerUiOptions + >, ) => { const { onChange, @@ -56,9 +59,9 @@ export const EntityPicker = ( formData, idSchema, } = props; - const allowedKinds = uiSchema['ui:options']?.allowedKinds; - const defaultKind = uiSchema['ui:options']?.defaultKind; - const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; + const allowedKinds = uiSchema?.['ui:options']?.allowedKinds; + const defaultKind = uiSchema?.['ui:options']?.defaultKind; + const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; const catalogApi = useApi(catalogApiRef); @@ -99,7 +102,7 @@ export const EntityPicker = ( onChange={onSelect} options={entityRefs || []} autoSelect - freeSolo={uiSchema['ui:options']?.allowArbitraryValues ?? true} + freeSolo={uiSchema?.['ui:options']?.allowArbitraryValues ?? true} renderInput={params => ( { const facet = 'metadata.tags'; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 2446883a86..d15677cfac 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -59,12 +59,12 @@ export const OwnedEntityPicker = ( formData, idSchema, } = props; + const allowedKinds = uiSchema?.['ui:options']?.allowedKinds; + const defaultKind = uiSchema?.['ui:options']?.defaultKind; + const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; - const allowedKinds = uiSchema['ui:options']?.allowedKinds; - const defaultKind = uiSchema['ui:options']?.defaultKind; - const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; const allowArbitraryValues = - uiSchema['ui:options']?.allowArbitraryValues ?? true; + uiSchema?.['ui:options']?.allowArbitraryValues ?? true; const { ownedEntities, loading } = useOwnedEntities(allowedKinds); const entityRefs = ownedEntities?.items diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx index 68c9723974..a94bdf74d7 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx @@ -36,7 +36,7 @@ describe('', () => { const rawErrors: string[] = []; const formData = undefined; - let props: FieldProps; + let props: FieldProps; const catalogApi: jest.Mocked = { getLocationById: jest.fn(), diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 3de2b12bd0..8ce01fc3ef 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -36,7 +36,7 @@ export interface OwnerPickerUiOptions { * @public */ export const OwnerPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps, ) => { const { schema: { title = 'Owner', description = 'The owner of the component' }, @@ -44,18 +44,18 @@ export const OwnerPicker = ( ...restProps } = props; - const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; + const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; const ownerUiSchema = { ...uiSchema, 'ui:options': { - allowedKinds: (uiSchema['ui:options']?.allowedKinds || [ + allowedKinds: (uiSchema?.['ui:options']?.allowedKinds || [ 'Group', 'User', ]) as string[], defaultKind: 'Group', allowArbitraryValues: - uiSchema['ui:options']?.allowArbitraryValues ?? true, + uiSchema?.['ui:options']?.allowArbitraryValues ?? true, ...(defaultNamespace !== undefined ? { defaultNamespace } : {}), }, }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index be7c05155c..0133c4a29c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -32,6 +32,7 @@ import { SecretsContext, } from '../../secrets/SecretsContext'; import { act, fireEvent } from '@testing-library/react'; +import { Field } from '@rjsf/utils'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial = { @@ -68,7 +69,7 @@ describe('RepoUrlPicker', () => { }} onSubmit={onSubmit} /> @@ -107,7 +108,7 @@ describe('RepoUrlPicker', () => { 'ui:field': 'RepoUrlPicker', 'ui:options': { allowedHosts: ['dev.azure.com'] }, }} - fields={{ RepoUrlPicker: RepoUrlPicker }} + fields={{ RepoUrlPicker: RepoUrlPicker as Field }} /> , @@ -145,7 +146,7 @@ describe('RepoUrlPicker', () => { }, }, }} - fields={{ RepoUrlPicker: RepoUrlPicker }} + fields={{ RepoUrlPicker: RepoUrlPicker as Field }} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 451df857e5..870ec9dd53 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -62,7 +62,10 @@ export interface RepoUrlPickerUiOptions { * @public */ export const RepoUrlPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + string | undefined, + RepoUrlPickerUiOptions + >, ) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index 55ba92abdc..ae4f2ec1b9 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -26,7 +26,7 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react'; * @public */ export const repoPickerValidation = ( - value: string, + value: string | undefined, validation: FieldValidation, context: { apiHolder: ApiHolder }, ) => { diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 60b9022053..21a565bf46 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -21,6 +21,7 @@ import { FieldExtensionComponentProps, } from './types'; import { Extension, attachComponentData } from '@backstage/core-plugin-api'; +import { UIOptionsType } from '@rjsf/utils'; export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1'; export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1'; @@ -39,7 +40,7 @@ export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null; */ export function createScaffolderFieldExtension< TReturnValue = unknown, - TInputProps = unknown, + TInputProps extends UIOptionsType = {}, >( options: FieldExtensionOptions, ): Extension> { diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index c3d99a6b37..85f8f99fd4 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -14,7 +14,13 @@ * limitations under the License. */ import { ApiHolder } from '@backstage/core-plugin-api'; -import { FieldValidation, FieldProps } from '@rjsf/utils'; +import { + FieldValidation, + FieldProps, + UIOptionsType, + UiSchema, +} from '@rjsf/utils'; +import { PropsWithChildren } from 'react'; /** * Field validation type for Custom Field Extensions. @@ -35,7 +41,7 @@ export type CustomFieldValidator = ( */ export type FieldExtensionOptions< TFieldReturnValue = unknown, - TInputProps = unknown, + TInputProps extends UIOptionsType = {}, > = { name: string; component: ( @@ -52,9 +58,9 @@ export type FieldExtensionOptions< */ export interface FieldExtensionComponentProps< TFieldReturnValue, - TUiOptions extends {} = {}, -> extends FieldProps { - uiSchema: FieldProps['uiSchema'] & { - 'ui:options'?: TUiOptions; + TUiOptions = {}, +> extends PropsWithChildren> { + uiSchema?: UiSchema & { + 'ui:options'?: TUiOptions & UIOptionsType; }; } diff --git a/yarn.lock b/yarn.lock index 8be2e5c8a6..441684ce3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6742,8 +6742,10 @@ __metadata: "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.57 "@react-hookz/web": ^15.0.0 - "@rjsf/core": ^3.2.1 - "@rjsf/material-ui": ^3.2.1 + "@rjsf/core": ^5.0.0-beta.2 + "@rjsf/material-ui": ^5.0.0-beta.2 + "@rjsf/utils": ^5.0.0-beta.2 + "@rjsf/validator-ajv6": ^5.0.0-beta.2 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 "@testing-library/react-hooks": ^8.0.0 @@ -11940,34 +11942,59 @@ __metadata: languageName: node linkType: hard -"@rjsf/core@npm:^3.2.1": - version: 3.2.1 - resolution: "@rjsf/core@npm:3.2.1" +"@rjsf/core@npm:^5.0.0-beta.2": + version: 5.0.0-beta.5 + resolution: "@rjsf/core@npm:5.0.0-beta.5" dependencies: - "@types/json-schema": ^7.0.7 - ajv: ^6.7.0 - core-js-pure: ^3.6.5 - json-schema-merge-allof: ^0.6.0 - jsonpointer: ^5.0.0 lodash: ^4.17.15 - nanoid: ^3.1.23 + lodash-es: ^4.17.15 + nanoid: ^3.3.4 prop-types: ^15.7.2 - react-is: ^16.9.0 peerDependencies: - react: ">=16" - checksum: 2142d4a31229ea242b79aca4ed93e2fe89e75f15ce93111457c3017d3ab295cae8f53e4dd870c619afa571959d00f46b3c19085c6a336f522c891fc07ecc46f1 + "@rjsf/utils": ^5.0.0-beta.1 + react: ^16.14.0 || >=17 + checksum: bf9538f98e6238b427afb5cccdcbb88dc7cf99d3dc9c25c8336c8cac6b82c6383fa29cb48139d312f737d668b4f836bca9fac4debebc8bd9ee46a3f8178ad37b languageName: node linkType: hard -"@rjsf/material-ui@npm:^3.2.1": - version: 3.2.1 - resolution: "@rjsf/material-ui@npm:3.2.1" +"@rjsf/material-ui@npm:^5.0.0-beta.2": + version: 5.0.0-beta.5 + resolution: "@rjsf/material-ui@npm:5.0.0-beta.5" peerDependencies: - "@material-ui/core": ^4.2.0 - "@material-ui/icons": ^4.2.1 - "@rjsf/core": ^3.0.0 - react: ">=16" - checksum: bd25cd9f2e2d568c653755e7268fe3e53279e1ae675e39bccd85f65557623d2052b706763e017a949f897751e25a16d0f2c8b995508bb56907be6786b09e2b1e + "@material-ui/core": ^4.12.3 + "@material-ui/icons": ^4.11.2 + "@rjsf/core": ^5.0.0-beta.1 + "@rjsf/utils": ^5.0.0-beta.1 + react: ^16.14.0 || >=17 + checksum: 2578ff81c9981ecefab6752a6de6f684cdc4ad0d5a3e4090456a42f811c055437b22ecb662610877598a640d624af9381ef3f15b93bb3a8abfae33770c51d194 + languageName: node + linkType: hard + +"@rjsf/utils@npm:^5.0.0-beta.2": + version: 5.0.0-beta.5 + resolution: "@rjsf/utils@npm:5.0.0-beta.5" + dependencies: + json-schema-merge-allof: ^0.8.1 + jsonpointer: ^5.0.1 + lodash: ^4.17.15 + lodash-es: ^4.17.15 + react-is: ^18.2.0 + peerDependencies: + react: ^16.14.0 || >=17 + checksum: 56549a1b86c068631e19f67c157fbe12e2ec99be2fcbe1b6375f7865b178b874ebce03a129064dcf2c2858b5991067dedac1e4490106269cf81dc4377717dcff + languageName: node + linkType: hard + +"@rjsf/validator-ajv6@npm:^5.0.0-beta.2": + version: 5.0.0-beta.5 + resolution: "@rjsf/validator-ajv6@npm:5.0.0-beta.5" + dependencies: + ajv: ^6.7.0 + lodash: ^4.17.15 + lodash-es: ^4.17.15 + peerDependencies: + "@rjsf/utils": ^5.0.0-beta.1 + checksum: 20e4bf431d8c805e180999c300819b049c52c19cff6f4b5e402edc8eb7757697dc1debb191ccf41bac63de88d067513ba33de958250d6e923f1d34fdaea7283c languageName: node linkType: hard @@ -18550,7 +18577,7 @@ __metadata: languageName: node linkType: hard -"compute-lcm@npm:^1.1.0, compute-lcm@npm:^1.1.2": +"compute-lcm@npm:^1.1.2": version: 1.1.2 resolution: "compute-lcm@npm:1.1.2" dependencies: @@ -18786,7 +18813,7 @@ __metadata: languageName: node linkType: hard -"core-js-pure@npm:^3.20.2, core-js-pure@npm:^3.6.5, core-js-pure@npm:^3.8.1": +"core-js-pure@npm:^3.20.2, core-js-pure@npm:^3.8.1": version: 3.21.1 resolution: "core-js-pure@npm:3.21.1" checksum: 00a5dff599b7fb0b30746a638b9d0edbdc0df24ed1580ca56be595fbe3c78c375d37fc4e1bff23627109229702c9ee8ea2587a66b8280eb33b85160aa4e401e9 @@ -22077,7 +22104,6 @@ __metadata: "@material-ui/lab": 4.0.0-alpha.57 "@octokit/rest": ^19.0.3 "@oriflame/backstage-plugin-score-card": ^0.5.1 - "@rjsf/core": ^3.2.1 "@roadiehq/backstage-plugin-buildkite": ^2.0.8 "@roadiehq/backstage-plugin-github-insights": ^2.0.5 "@roadiehq/backstage-plugin-github-pull-requests": ^2.2.7 @@ -27172,17 +27198,6 @@ __metadata: languageName: node linkType: hard -"json-schema-merge-allof@npm:^0.6.0": - version: 0.6.0 - resolution: "json-schema-merge-allof@npm:0.6.0" - dependencies: - compute-lcm: ^1.1.0 - json-schema-compare: ^0.2.2 - lodash: ^4.17.4 - checksum: 2008aede3f5d05d7870e7d5e554e5c6a5b451cfff1357d34d3d8b34e2ba57468a97c76aa5b967bdb411d91b98c734f19f350de578d25b2a0a27cd4e1ca92bd1d - languageName: node - linkType: hard - "json-schema-merge-allof@npm:^0.8.1": version: 0.8.1 resolution: "json-schema-merge-allof@npm:0.8.1" @@ -27359,10 +27374,10 @@ __metadata: languageName: node linkType: hard -"jsonpointer@npm:^5.0.0": - version: 5.0.0 - resolution: "jsonpointer@npm:5.0.0" - checksum: c7ec0b6bb596b81de687bc12945586bbcdc80dfb54919656d2690d76334f796a936270067ee9f1b5bbc2d9ecc551afb366ac35e6685aa61f07b5b68d1e5e857d +"jsonpointer@npm:^5.0.1": + version: 5.0.1 + resolution: "jsonpointer@npm:5.0.1" + checksum: 0b40f712900ad0c846681ea2db23b6684b9d5eedf55807b4708c656f5894b63507d0e28ae10aa1bddbea551241035afe62b6df0800fc94c2e2806a7f3adecd7c languageName: node linkType: hard @@ -28097,7 +28112,7 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:^4.17.21": +"lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 @@ -30062,15 +30077,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.1.23": - version: 3.3.1 - resolution: "nanoid@npm:3.3.1" - bin: - nanoid: bin/nanoid.cjs - checksum: 4ef0969e1bbe866fc223eb32276cbccb0961900bfe79104fa5abe34361979dead8d0e061410a5c03bc3d47455685adf32c09d6f27790f4a6898fb51f7df7ec86 - languageName: node - linkType: hard - "nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" @@ -33568,7 +33574,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.6, react-is@npm:^16.9.0": +"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.6": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -33589,6 +33595,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.2.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "react-lifecycles-compat@npm:^3.0.4": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" From adfb075796e5022a16cfaa6311c7fd6cedcbf0e2 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 8 Sep 2022 14:25:09 +0200 Subject: [PATCH 107/160] chore: one last error Signed-off-by: blam --- .../src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx | 5 +---- .../src/next/TemplateWizardPage/Stepper/Stepper.tsx | 6 +++--- .../src/next/TemplateWizardPage/Stepper/schema.ts | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 870ec9dd53..451df857e5 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -62,10 +62,7 @@ export interface RepoUrlPickerUiOptions { * @public */ export const RepoUrlPicker = ( - props: FieldExtensionComponentProps< - string | undefined, - RepoUrlPickerUiOptions - >, + props: FieldExtensionComponentProps, ) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index ccdf32542a..ade7c982ff 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -23,7 +23,7 @@ import { makeStyles, } from '@material-ui/core'; import { withTheme } from '@rjsf/core'; -import { FieldValidation } from '@rjsf/utils'; +import { ErrorSchema, FieldValidation } from '@rjsf/utils'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useMemo, useState } from 'react'; import { FieldExtensionOptions } from '../../../extensions'; @@ -95,7 +95,7 @@ export const Stepper = (props: StepperProps) => { const returnedValidation = await validation(formData); const hasErrors = Object.values(returnedValidation).some(i => { - return i.__errors.length > 0; + return i.__errors?.length! > 0 ?? false; }); if (hasErrors) { @@ -127,7 +127,7 @@ export const Stepper = (props: StepperProps) => { {activeStep < steps.length ? ( { const fieldValidation: FieldValidation = { __errors: [] as string[], addError: (message: string) => { - fieldValidation.__errors.push(message); + fieldValidation.__errors?.push(message); }, }; From 7443c37ffc2a07fffe3a9aa2fdde8e682db9b596 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 12 Sep 2022 10:49:14 +0200 Subject: [PATCH 108/160] chore: fixing some typescripty things Signed-off-by: blam --- .../components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx | 5 ++++- .../src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 0133c4a29c..9f126f52a0 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/integration-react'; import { scaffolderApiRef } from '../../../api'; import { ScaffolderApi } from '../../../types'; - +import validator from '@rjsf/validator-ajv6'; import { SecretsContextProvider, SecretsContext, @@ -67,6 +67,7 @@ describe('RepoUrlPicker', () => { > }} @@ -103,6 +104,7 @@ describe('RepoUrlPicker', () => { > { > , + props: FieldExtensionComponentProps< + string | undefined, + RepoUrlPickerUiOptions + >, ) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( From ffe1357499af9b5fb22a8121e877b0c58c4e6360 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Sep 2022 10:40:13 +0200 Subject: [PATCH 109/160] chore: fix Signed-off-by: blam --- plugins/scaffolder/package.json | 10 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 2 - .../RepoUrlPicker/RepoUrlPicker.test.tsx | 2 - plugins/scaffolder/src/extensions/types.ts | 13 +-- yarn.lock | 104 +++++++++++++----- 5 files changed, 88 insertions(+), 43 deletions(-) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index cf28227e8e..6508ae77e7 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -54,10 +54,12 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@react-hookz/web": "^15.0.0", - "@rjsf/core": "^5.0.0-beta.2", - "@rjsf/material-ui": "^5.0.0-beta.2", - "@rjsf/utils": "^5.0.0-beta.2", - "@rjsf/validator-ajv6": "^5.0.0-beta.2", + "@rjsf/core": "^3.2.1", + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.9", + "@rjsf/material-ui": "^3.2.1", + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.9", + "@rjsf/utils": "^5.0.0-beta.9", + "@rjsf/validator-ajv8": "^5.0.0-beta.9", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 064299f667..7ab3007e99 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -32,7 +32,6 @@ import { import { UiSchema } from '@rjsf/utils'; import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; import { Theme } from '@rjsf/material-ui'; -import validator from '@rjsf/validator-ajv6'; import React, { useState } from 'react'; import { transformSchemaToProps } from './schema'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; @@ -208,7 +207,6 @@ export const MultistepJsonForm = (props: Props) => { { > }} diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 85f8f99fd4..4758d96f66 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -14,12 +14,7 @@ * limitations under the License. */ import { ApiHolder } from '@backstage/core-plugin-api'; -import { - FieldValidation, - FieldProps, - UIOptionsType, - UiSchema, -} from '@rjsf/utils'; +import { FieldValidation, FieldProps, UiSchema } from '@rjsf/core'; import { PropsWithChildren } from 'react'; /** @@ -41,7 +36,7 @@ export type CustomFieldValidator = ( */ export type FieldExtensionOptions< TFieldReturnValue = unknown, - TInputProps extends UIOptionsType = {}, + TInputProps extends {} = {}, > = { name: string; component: ( @@ -60,7 +55,7 @@ export interface FieldExtensionComponentProps< TFieldReturnValue, TUiOptions = {}, > extends PropsWithChildren> { - uiSchema?: UiSchema & { - 'ui:options'?: TUiOptions & UIOptionsType; + uiSchema: UiSchema & { + 'ui:options': TUiOptions; }; } diff --git a/yarn.lock b/yarn.lock index 441684ce3d..dcecfe2b9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6742,10 +6742,12 @@ __metadata: "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.57 "@react-hookz/web": ^15.0.0 - "@rjsf/core": ^5.0.0-beta.2 - "@rjsf/material-ui": ^5.0.0-beta.2 - "@rjsf/utils": ^5.0.0-beta.2 - "@rjsf/validator-ajv6": ^5.0.0-beta.2 + "@rjsf/core": ^3.2.1 + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.9" + "@rjsf/material-ui": ^3.2.1 + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.9" + "@rjsf/utils": ^5.0.0-beta.9 + "@rjsf/validator-ajv8": ^5.0.0-beta.9 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 "@testing-library/react-hooks": ^8.0.0 @@ -11942,9 +11944,9 @@ __metadata: languageName: node linkType: hard -"@rjsf/core@npm:^5.0.0-beta.2": - version: 5.0.0-beta.5 - resolution: "@rjsf/core@npm:5.0.0-beta.5" +"@rjsf/core-v5@npm:@rjsf/core@^5.0.0-beta.9": + version: 5.0.0-beta.9 + resolution: "@rjsf/core@npm:5.0.0-beta.9" dependencies: lodash: ^4.17.15 lodash-es: ^4.17.15 @@ -11953,26 +11955,57 @@ __metadata: peerDependencies: "@rjsf/utils": ^5.0.0-beta.1 react: ^16.14.0 || >=17 - checksum: bf9538f98e6238b427afb5cccdcbb88dc7cf99d3dc9c25c8336c8cac6b82c6383fa29cb48139d312f737d668b4f836bca9fac4debebc8bd9ee46a3f8178ad37b + checksum: b55e1a4b3f9f638afdeda1da0abeb145040081201b2f9ed680c93d3dea2ee20e1066da5d2c32c69916d9f2fce422ecb8058f303b9f4df1b20dacf35564f5c7ec languageName: node linkType: hard -"@rjsf/material-ui@npm:^5.0.0-beta.2": - version: 5.0.0-beta.5 - resolution: "@rjsf/material-ui@npm:5.0.0-beta.5" +"@rjsf/core@npm:^3.2.1": + version: 3.2.1 + resolution: "@rjsf/core@npm:3.2.1" + dependencies: + "@types/json-schema": ^7.0.7 + ajv: ^6.7.0 + core-js-pure: ^3.6.5 + json-schema-merge-allof: ^0.6.0 + jsonpointer: ^5.0.0 + lodash: ^4.17.15 + nanoid: ^3.1.23 + prop-types: ^15.7.2 + react-is: ^16.9.0 + peerDependencies: + react: ">=16" + checksum: 2142d4a31229ea242b79aca4ed93e2fe89e75f15ce93111457c3017d3ab295cae8f53e4dd870c619afa571959d00f46b3c19085c6a336f522c891fc07ecc46f1 + languageName: node + linkType: hard + +"@rjsf/material-ui-v5@npm:@rjsf/material-ui@^5.0.0-beta.9": + version: 5.0.0-beta.9 + resolution: "@rjsf/material-ui@npm:5.0.0-beta.9" peerDependencies: "@material-ui/core": ^4.12.3 "@material-ui/icons": ^4.11.2 "@rjsf/core": ^5.0.0-beta.1 "@rjsf/utils": ^5.0.0-beta.1 react: ^16.14.0 || >=17 - checksum: 2578ff81c9981ecefab6752a6de6f684cdc4ad0d5a3e4090456a42f811c055437b22ecb662610877598a640d624af9381ef3f15b93bb3a8abfae33770c51d194 + checksum: 44624d1aff0f30963d7ae2ab529713d9fcf6e6df809264d6bdee7ef5e71934ab031dbba9dcc9c10be05cb7b0fd679f6fbb1eb27ce7081940b56034a5f2564093 languageName: node linkType: hard -"@rjsf/utils@npm:^5.0.0-beta.2": - version: 5.0.0-beta.5 - resolution: "@rjsf/utils@npm:5.0.0-beta.5" +"@rjsf/material-ui@npm:^3.2.1": + version: 3.2.1 + resolution: "@rjsf/material-ui@npm:3.2.1" + peerDependencies: + "@material-ui/core": ^4.2.0 + "@material-ui/icons": ^4.2.1 + "@rjsf/core": ^3.0.0 + react: ">=16" + checksum: bd25cd9f2e2d568c653755e7268fe3e53279e1ae675e39bccd85f65557623d2052b706763e017a949f897751e25a16d0f2c8b995508bb56907be6786b09e2b1e + languageName: node + linkType: hard + +"@rjsf/utils@npm:^5.0.0-beta.9": + version: 5.0.0-beta.9 + resolution: "@rjsf/utils@npm:5.0.0-beta.9" dependencies: json-schema-merge-allof: ^0.8.1 jsonpointer: ^5.0.1 @@ -11981,20 +12014,21 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^16.14.0 || >=17 - checksum: 56549a1b86c068631e19f67c157fbe12e2ec99be2fcbe1b6375f7865b178b874ebce03a129064dcf2c2858b5991067dedac1e4490106269cf81dc4377717dcff + checksum: ac5b99cf7860c838e5b509a523b19824f4966ec8d44284a026bf81de7c7733bac40432af4c67ab538bd400f529dc55d9001e07618a514cac365cd604ff786b11 languageName: node linkType: hard -"@rjsf/validator-ajv6@npm:^5.0.0-beta.2": - version: 5.0.0-beta.5 - resolution: "@rjsf/validator-ajv6@npm:5.0.0-beta.5" +"@rjsf/validator-ajv8@npm:^5.0.0-beta.9": + version: 5.0.0-beta.9 + resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.9" dependencies: - ajv: ^6.7.0 + ajv: ^8.11.0 + ajv-formats: ^2.1.1 lodash: ^4.17.15 lodash-es: ^4.17.15 peerDependencies: "@rjsf/utils": ^5.0.0-beta.1 - checksum: 20e4bf431d8c805e180999c300819b049c52c19cff6f4b5e402edc8eb7757697dc1debb191ccf41bac63de88d067513ba33de958250d6e923f1d34fdaea7283c + checksum: 64ca81f1f440b08f959d61e1f97b25d890cd8f03582287004961d0ba0ec7d6719748b00d821ef009d330816ca2e4e030d46fa695a3c12c338518e469242694cd languageName: node linkType: hard @@ -15653,7 +15687,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.6.3, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0, ajv@npm:^8.6.3, ajv@npm:^8.8.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" dependencies: @@ -18577,7 +18611,7 @@ __metadata: languageName: node linkType: hard -"compute-lcm@npm:^1.1.2": +"compute-lcm@npm:^1.1.0, compute-lcm@npm:^1.1.2": version: 1.1.2 resolution: "compute-lcm@npm:1.1.2" dependencies: @@ -18820,6 +18854,13 @@ __metadata: languageName: node linkType: hard +"core-js-pure@npm:^3.6.5": + version: 3.25.3 + resolution: "core-js-pure@npm:3.25.3" + checksum: be3097b8e2b541977a08004e433b68adb4951cc7694229e695ae407f296d370c1f360e5fd70fc6910c4427e46e48e087ac455d68cebdf4314370907d2e03eef1 + languageName: node + linkType: hard + "core-js@npm:^2.4.0, core-js@npm:^2.5.0, core-js@npm:^2.6.10": version: 2.6.12 resolution: "core-js@npm:2.6.12" @@ -27198,6 +27239,17 @@ __metadata: languageName: node linkType: hard +"json-schema-merge-allof@npm:^0.6.0": + version: 0.6.0 + resolution: "json-schema-merge-allof@npm:0.6.0" + dependencies: + compute-lcm: ^1.1.0 + json-schema-compare: ^0.2.2 + lodash: ^4.17.4 + checksum: 2008aede3f5d05d7870e7d5e554e5c6a5b451cfff1357d34d3d8b34e2ba57468a97c76aa5b967bdb411d91b98c734f19f350de578d25b2a0a27cd4e1ca92bd1d + languageName: node + linkType: hard + "json-schema-merge-allof@npm:^0.8.1": version: 0.8.1 resolution: "json-schema-merge-allof@npm:0.8.1" @@ -27374,7 +27426,7 @@ __metadata: languageName: node linkType: hard -"jsonpointer@npm:^5.0.1": +"jsonpointer@npm:^5.0.0, jsonpointer@npm:^5.0.1": version: 5.0.1 resolution: "jsonpointer@npm:5.0.1" checksum: 0b40f712900ad0c846681ea2db23b6684b9d5eedf55807b4708c656f5894b63507d0e28ae10aa1bddbea551241035afe62b6df0800fc94c2e2806a7f3adecd7c @@ -30077,7 +30129,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.4": +"nanoid@npm:^3.1.23, nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" bin: @@ -33574,7 +33626,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.6": +"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.6, react-is@npm:^16.9.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f From d362737143a2a9be3c17a2b79d67a9b29a408b7a Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Sep 2022 14:24:52 +0200 Subject: [PATCH 110/160] chore: reset some things so we can run it in paralell Signed-off-by: blam --- .../FieldOverrides/DescriptionField.tsx | 2 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 9 ++- .../TemplatePage/createValidator.test.ts | 4 +- .../TemplatePage/createValidator.ts | 4 +- .../fields/EntityPicker/EntityPicker.test.tsx | 8 +-- .../fields/EntityPicker/EntityPicker.tsx | 13 ++-- .../fields/OwnerPicker/OwnerPicker.test.tsx | 4 +- .../fields/OwnerPicker/OwnerPicker.tsx | 8 +-- .../RepoUrlPicker/RepoUrlPicker.test.tsx | 15 ++--- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 5 +- .../fields/RepoUrlPicker/validation.test.ts | 2 +- .../fields/RepoUrlPicker/validation.ts | 4 +- plugins/scaffolder/src/extensions/index.tsx | 34 ++++++++++- plugins/scaffolder/src/extensions/types.ts | 61 +++++++++++++++++-- .../TemplateWizardPage/Stepper/Stepper.tsx | 6 +- .../Stepper/createAsyncValidators.test.ts | 13 ++-- .../Stepper/createAsyncValidators.ts | 4 +- 17 files changed, 134 insertions(+), 62 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx index eded38beee..3c592860ef 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { MarkdownContent } from '@backstage/core-components'; -import { FieldProps } from '@rjsf/utils'; +import { FieldProps } from '@rjsf/core'; export const DescriptionField = ({ description }: FieldProps) => description && ; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 7ab3007e99..8c71df67b6 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -29,9 +29,8 @@ import { useApi, featureFlagsApiRef, } from '@backstage/core-plugin-api'; -import { UiSchema } from '@rjsf/utils'; -import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; -import { Theme } from '@rjsf/material-ui'; +import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; +import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useState } from 'react'; import { transformSchemaToProps } from './schema'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; @@ -39,6 +38,7 @@ import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; +const Form = withTheme(MuiTheme); type Step = { schema: JsonObject; title: string; @@ -123,7 +123,6 @@ export const MultistepJsonForm = (props: Props) => { finishButtonLabel, layouts, } = props; - const Form = withTheme(Theme); const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -214,7 +213,7 @@ export const MultistepJsonForm = (props: Props) => { formData={formData} formContext={{ formData }} onChange={onChange} - onSubmit={(e: any) => { + onSubmit={e => { if (e.errors.length === 0) handleNext(); }} {...formProps} diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts index eea5efc296..f549c9e7f2 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts @@ -17,7 +17,7 @@ import { createValidator } from './createValidator'; import { CustomFieldValidator } from '../../extensions'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { FormValidation } from '@rjsf/utils'; +import { FormValidation } from '@rjsf/core'; describe('createValidator', () => { const validators: Record> = @@ -68,6 +68,6 @@ describe('createValidator', () => { /* THEN */ expect(result).not.toBeNull(); - expect(result.p1?.addError).toHaveBeenCalledTimes(1); + expect(result.p1.addError).toHaveBeenCalledTimes(1); }); }); diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts index d1c55009a9..9cd1489a02 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts @@ -15,7 +15,7 @@ */ import { CustomFieldValidator } from '../../extensions'; -import { FormValidation } from '@rjsf/utils'; +import { FormValidation } from '@rjsf/core'; import { JsonObject, JsonValue } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; @@ -62,7 +62,7 @@ export const createValidator = ( if (fieldName && typeof validators[fieldName] === 'function') { validators[fieldName]!( propData as JsonValue, - propValidation as FormValidation, + propValidation, context, ); } diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index ffc447577a..942eef9d82 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { FieldProps } from '@rjsf/utils'; +import { FieldProps } from '@rjsf/core'; import { fireEvent } from '@testing-library/react'; import React from 'react'; import { EntityPicker } from './EntityPicker'; @@ -39,7 +39,7 @@ describe('', () => { const rawErrors: string[] = []; const formData = undefined; - let props: FieldProps; + let props: FieldProps; const catalogApi: jest.Mocked = { getLocationById: jest.fn(), @@ -76,7 +76,7 @@ describe('', () => { uiSchema, rawErrors, formData, - } as any; + } as unknown as FieldProps; catalogApi.getEntities.mockResolvedValue({ items: entities }); }); @@ -117,7 +117,7 @@ describe('', () => { uiSchema, rawErrors, formData, - } as any; + } as unknown as FieldProps; catalogApi.getEntities.mockResolvedValue({ items: entities }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index a040320d79..e7883a4721 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -45,10 +45,7 @@ export interface EntityPickerUiOptions { * @public */ export const EntityPicker = ( - props: FieldExtensionComponentProps< - string | undefined, - EntityPickerUiOptions - >, + props: FieldExtensionComponentProps, ) => { const { onChange, @@ -59,9 +56,9 @@ export const EntityPicker = ( formData, idSchema, } = props; - const allowedKinds = uiSchema?.['ui:options']?.allowedKinds; - const defaultKind = uiSchema?.['ui:options']?.defaultKind; - const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; + const allowedKinds = uiSchema['ui:options']?.allowedKinds; + const defaultKind = uiSchema['ui:options']?.defaultKind; + const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; const catalogApi = useApi(catalogApiRef); @@ -102,7 +99,7 @@ export const EntityPicker = ( onChange={onSelect} options={entityRefs || []} autoSelect - freeSolo={uiSchema?.['ui:options']?.allowArbitraryValues ?? true} + freeSolo={uiSchema['ui:options']?.allowArbitraryValues ?? true} renderInput={params => ( ', () => { const rawErrors: string[] = []; const formData = undefined; - let props: FieldProps; + let props: FieldProps; const catalogApi: jest.Mocked = { getLocationById: jest.fn(), diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 8ce01fc3ef..3de2b12bd0 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -36,7 +36,7 @@ export interface OwnerPickerUiOptions { * @public */ export const OwnerPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps, ) => { const { schema: { title = 'Owner', description = 'The owner of the component' }, @@ -44,18 +44,18 @@ export const OwnerPicker = ( ...restProps } = props; - const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; + const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; const ownerUiSchema = { ...uiSchema, 'ui:options': { - allowedKinds: (uiSchema?.['ui:options']?.allowedKinds || [ + allowedKinds: (uiSchema['ui:options']?.allowedKinds || [ 'Group', 'User', ]) as string[], defaultKind: 'Group', allowArbitraryValues: - uiSchema?.['ui:options']?.allowArbitraryValues ?? true, + uiSchema['ui:options']?.allowArbitraryValues ?? true, ...(defaultNamespace !== undefined ? { defaultNamespace } : {}), }, }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index f28b4bc816..7fac9f7840 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -15,8 +15,7 @@ */ import React, { useContext } from 'react'; import { RepoUrlPicker } from './RepoUrlPicker'; -import { withTheme } from '@rjsf/core'; -import { Theme } from '@rjsf/material-ui'; +import Form from '@rjsf/core'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { scmIntegrationsApiRef, @@ -26,12 +25,12 @@ import { } from '@backstage/integration-react'; import { scaffolderApiRef } from '../../../api'; import { ScaffolderApi } from '../../../types'; + import { SecretsContextProvider, SecretsContext, } from '../../secrets/SecretsContext'; import { act, fireEvent } from '@testing-library/react'; -import { Field } from '@rjsf/utils'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial = { @@ -51,8 +50,6 @@ describe('RepoUrlPicker', () => { getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }), }; - const Form = withTheme(Theme); - describe('happy path rendering', () => { it('should render the repo url picker with minimal props', async () => { const onSubmit = jest.fn(); @@ -68,7 +65,7 @@ describe('RepoUrlPicker', () => { }} + fields={{ RepoUrlPicker: RepoUrlPicker }} onSubmit={onSubmit} /> @@ -102,13 +99,12 @@ describe('RepoUrlPicker', () => { > }} + fields={{ RepoUrlPicker: RepoUrlPicker }} /> , @@ -136,7 +132,6 @@ describe('RepoUrlPicker', () => { > { }, }, }} - fields={{ RepoUrlPicker: RepoUrlPicker as Field }} + fields={{ RepoUrlPicker: RepoUrlPicker }} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 870ec9dd53..451df857e5 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -62,10 +62,7 @@ export interface RepoUrlPickerUiOptions { * @public */ export const RepoUrlPicker = ( - props: FieldExtensionComponentProps< - string | undefined, - RepoUrlPickerUiOptions - >, + props: FieldExtensionComponentProps, ) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts index b9c481957c..45e44b4072 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts @@ -15,7 +15,7 @@ */ import { repoPickerValidation } from './validation'; -import { FieldValidation } from '@rjsf/utils'; +import { FieldValidation } from '@rjsf/core'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/core-app-api'; import { ApiHolder } from '@backstage/core-plugin-api'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index ae4f2ec1b9..99847f9e3b 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/utils'; +import { FieldValidation } from '@rjsf/core'; import { ApiHolder } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -26,7 +26,7 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react'; * @public */ export const repoPickerValidation = ( - value: string | undefined, + value: string, validation: FieldValidation, context: { apiHolder: ApiHolder }, ) => { diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 21a565bf46..38cabd5511 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -19,6 +19,9 @@ import { CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, + NextCustomFieldValidator, + NextFieldExtensionOptions, + NextFieldExtensionComponentProps, } from './types'; import { Extension, attachComponentData } from '@backstage/core-plugin-api'; import { UIOptionsType } from '@rjsf/utils'; @@ -40,7 +43,7 @@ export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null; */ export function createScaffolderFieldExtension< TReturnValue = unknown, - TInputProps extends UIOptionsType = {}, + TInputProps = unknown, >( options: FieldExtensionOptions, ): Extension> { @@ -59,6 +62,32 @@ export function createScaffolderFieldExtension< }; } +/** + * Method for creating field extensions that can be used in the scaffolder + * frontend form. + * @alpha + */ +export function createNextScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps extends UIOptionsType = {}, +>( + options: FieldExtensionOptions, +): Extension> { + return { + expose() { + const FieldExtensionDataHolder: any = () => null; + + attachComponentData( + FieldExtensionDataHolder, + FIELD_EXTENSION_KEY, + options, + ); + + return FieldExtensionDataHolder; + }, + }; +} + /** * The Wrapping component for defining fields extensions inside * @@ -77,6 +106,9 @@ export type { CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, + NextCustomFieldValidator, + NextFieldExtensionOptions, + NextFieldExtensionComponentProps, }; export { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default'; diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 4758d96f66..627587bba6 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -14,9 +14,16 @@ * limitations under the License. */ import { ApiHolder } from '@backstage/core-plugin-api'; -import { FieldValidation, FieldProps, UiSchema } from '@rjsf/core'; +import { FieldValidation, FieldProps } from '@rjsf/core'; import { PropsWithChildren } from 'react'; +import { + UIOptionsType, + FieldProps as FieldPropsV5, + UiSchema as UiSchemaV5, + FieldValidation as FieldValidationV5, +} from '@rjsf/utils'; + /** * Field validation type for Custom Field Extensions. * @@ -36,7 +43,7 @@ export type CustomFieldValidator = ( */ export type FieldExtensionOptions< TFieldReturnValue = unknown, - TInputProps extends {} = {}, + TInputProps = unknown, > = { name: string; component: ( @@ -53,9 +60,51 @@ export type FieldExtensionOptions< */ export interface FieldExtensionComponentProps< TFieldReturnValue, - TUiOptions = {}, -> extends PropsWithChildren> { - uiSchema: UiSchema & { - 'ui:options': TUiOptions; + TUiOptions extends {} = {}, +> extends FieldProps { + uiSchema: FieldProps['uiSchema'] & { + 'ui:options'?: TUiOptions; }; } + +/** + * Type for Field Extension Props for RJSF v5 + * + * @alpha + */ +export interface NextFieldExtensionComponentProps< + TFieldReturnValue, + TUiOptions = {}, +> extends PropsWithChildren> { + uiSchema?: UiSchemaV5 & { + 'ui:options'?: TUiOptions & UIOptionsType; + }; +} + +/** + * Field validation type for Custom Field Extensions. + * + * @alpha + */ +export type NextCustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidationV5, + context: { apiHolder: ApiHolder }, +) => void | Promise; + +/** + * Type for the Custom Field Extension with the + * name and components and validation function. + * + * @alpha + */ +export type NextFieldExtensionOptions< + TFieldReturnValue = unknown, + TInputProps = unknown, +> = { + name: string; + component: ( + props: FieldExtensionComponentProps, + ) => JSX.Element | null; + validation?: NextCustomFieldValidator; +}; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index ade7c982ff..aa3a384738 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -26,12 +26,12 @@ import { withTheme } from '@rjsf/core'; import { ErrorSchema, FieldValidation } from '@rjsf/utils'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useMemo, useState } from 'react'; -import { FieldExtensionOptions } from '../../../extensions'; +import { NextFieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; -import validator from '@rjsf/validator-ajv6'; +import validator from '@rjsf/validator-ajv8'; const useStyles = makeStyles(theme => ({ backButton: { @@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({ export interface StepperProps { manifest: TemplateParameterSchema; - extensions: FieldExtensionOptions[]; + extensions: NextFieldExtensionOptions[]; } const Form = withTheme(MuiTheme); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts index ed887fb56d..a4a937e6fe 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { createAsyncValidators } from './createAsyncValidators'; describe('createAsyncValidators', () => { @@ -79,13 +79,16 @@ describe('createAsyncValidators', () => { }, }; - const NameField: CustomFieldValidator = (value, { addError }) => { + const NameField: NextCustomFieldValidator = ( + value, + { addError }, + ) => { if (!value) { addError('something is broken here!'); } }; - const AddressField: CustomFieldValidator<{ + const AddressField: NextCustomFieldValidator<{ street?: string; postcode?: string; }> = (value, { addError }) => { @@ -101,8 +104,8 @@ describe('createAsyncValidators', () => { const validate = createAsyncValidators( schema, { - NameField: NameField as CustomFieldValidator, - AddressField: AddressField as CustomFieldValidator, + NameField: NameField as NextCustomFieldValidator, + AddressField: AddressField as NextCustomFieldValidator, }, { apiHolder: { get: jest.fn() }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts index 60eed0f364..1355387eda 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts @@ -17,13 +17,13 @@ import { FieldValidation } from '@rjsf/utils'; import { JsonObject } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { Draft07 as JSONSchema } from 'json-schema-library'; import { createFieldValidation } from './schema'; export const createAsyncValidators = ( rootSchema: JsonObject, - validators: Record>, + validators: Record>, context: { apiHolder: ApiHolder; }, From 2548b5a5909ecf95e97eb0138806fcb9000a1e77 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 28 Sep 2022 18:57:46 +0200 Subject: [PATCH 111/160] chore: typescript is happy. ring the police. Signed-off-by: blam Signed-off-by: blam --- plugins/scaffolder/src/extensions/types.ts | 2 +- plugins/scaffolder/src/next/Router/Router.tsx | 6 +++--- .../src/next/TemplateWizardPage/Stepper/Stepper.tsx | 7 ++++--- .../src/next/TemplateWizardPage/TemplateWizardPage.tsx | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 627587bba6..62f17dff15 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -104,7 +104,7 @@ export type NextFieldExtensionOptions< > = { name: string; component: ( - props: FieldExtensionComponentProps, + props: NextFieldExtensionComponentProps, ) => JSX.Element | null; validation?: NextCustomFieldValidator; }; diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 758e268cb3..f0e5823f84 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -18,10 +18,10 @@ import { Routes, Route, useOutlet } from 'react-router'; import { TemplateListPage } from '../TemplateListPage'; import { TemplateWizardPage } from '../TemplateWizardPage'; import { - FieldExtensionOptions, FIELD_EXTENSION_WRAPPER_KEY, FIELD_EXTENSION_KEY, DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, + NextFieldExtensionOptions, } from '../../extensions'; import { useElementFilter } from '@backstage/core-plugin-api'; @@ -60,7 +60,7 @@ export const Router = (props: PropsWithChildren) => { .selectByComponentData({ key: FIELD_EXTENSION_WRAPPER_KEY, }) - .findComponentData({ + .findComponentData({ key: FIELD_EXTENSION_KEY, }), ); @@ -73,7 +73,7 @@ export const Router = (props: PropsWithChildren) => { customFieldExtension => customFieldExtension.name === name, ), ), - ]; + ] as NextFieldExtensionOptions[]; // here just to coerce the default extensions to the correct type return ( diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index aa3a384738..2735ea7e61 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -22,9 +22,9 @@ import { Button, makeStyles, } from '@material-ui/core'; -import { withTheme } from '@rjsf/core'; +import { withTheme } from '@rjsf/core-v5'; import { ErrorSchema, FieldValidation } from '@rjsf/utils'; -import { Theme as MuiTheme } from '@rjsf/material-ui'; +import { Theme as MuiTheme } from '@rjsf/material-ui-v5'; import React, { useMemo, useState } from 'react'; import { NextFieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; @@ -53,7 +53,8 @@ export interface StepperProps { extensions: NextFieldExtensionOptions[]; } -const Form = withTheme(MuiTheme); +// This needs an any for some reason, think it coul +const Form = withTheme(MuiTheme as any); export const Stepper = (props: StepperProps) => { const { steps } = useTemplateSchema(props.manifest); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index 468db438d5..f6739e6389 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -22,7 +22,7 @@ import { InfoCard, MarkdownContent, } from '@backstage/core-components'; -import { FieldExtensionOptions } from '../../extensions'; +import { NextFieldExtensionOptions } from '../../extensions'; import { Navigate } from 'react-router'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { @@ -39,7 +39,7 @@ import { BackstageTheme } from '@backstage/theme'; import { nextRouteRef, selectedTemplateRouteRef } from '../../routes'; export interface TemplateWizardPageProps { - customFieldExtensions: FieldExtensionOptions[]; + customFieldExtensions: NextFieldExtensionOptions[]; } const useStyles = makeStyles(() => ({ From 2d58be910dc63b5fba08bc2f58bea2782b1b4a2c Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 29 Sep 2022 10:15:48 +0200 Subject: [PATCH 112/160] chore: resetting the fields Signed-off-by: blam --- .../components/scaffolder/customScaffolderExtensions.tsx | 4 ++-- .../fields/EntityNamePicker/EntityNamePicker.tsx | 2 +- .../components/fields/EntityNamePicker/validation.test.ts | 2 +- .../src/components/fields/EntityNamePicker/validation.ts | 2 +- .../fields/EntityTagsPicker/EntityTagsPicker.tsx | 6 +++--- .../fields/OwnedEntityPicker/OwnedEntityPicker.tsx | 8 ++++---- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx index 3aadd69c05..2a4dedf080 100644 --- a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx +++ b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import type { FieldValidation } from '@rjsf/utils'; +import type { FieldValidation } from '@rjsf/core'; import { createScaffolderFieldExtension, FieldExtensionComponentProps, @@ -30,7 +30,7 @@ const TextValuePicker = (props: FieldExtensionComponentProps) => { schema: { title, description }, rawErrors, formData, - uiSchema: { 'ui:autofocus': autoFocus } = {}, + uiSchema: { 'ui:autofocus': autoFocus }, idSchema, placeholder, } = props; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 2b21141edb..802135a863 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -29,7 +29,7 @@ export const EntityNamePicker = ( schema: { title = 'Name', description = 'Unique name of the component' }, rawErrors, formData, - uiSchema: { 'ui:autofocus': autoFocus } = {}, + uiSchema: { 'ui:autofocus': autoFocus }, idSchema, placeholder, } = props; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts index 7201c9c4b3..290650387e 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/utils'; +import { FieldValidation } from '@rjsf/core'; import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; import { entityNamePickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts index 1533b0cdc3..7a40a460ba 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/utils'; +import { FieldValidation } from '@rjsf/core'; import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; export const entityNamePickerValidation = ( diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index fcbf7a7c80..c0e3aab436 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -51,9 +51,9 @@ export const EntityTagsPicker = ( const [inputValue, setInputValue] = useState(''); const [inputError, setInputError] = useState(false); const tagValidator = makeValidator().isValidTag; - const kinds = uiSchema?.['ui:options']?.kinds; - const showCounts = uiSchema?.['ui:options']?.showCounts; - const helperText = uiSchema?.['ui:options']?.helperText; + const kinds = uiSchema['ui:options']?.kinds; + const showCounts = uiSchema['ui:options']?.showCounts; + const helperText = uiSchema['ui:options']?.helperText; const { loading, value: existingTags } = useAsync(async () => { const facet = 'metadata.tags'; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index d15677cfac..2446883a86 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -59,12 +59,12 @@ export const OwnedEntityPicker = ( formData, idSchema, } = props; - const allowedKinds = uiSchema?.['ui:options']?.allowedKinds; - const defaultKind = uiSchema?.['ui:options']?.defaultKind; - const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; + const allowedKinds = uiSchema['ui:options']?.allowedKinds; + const defaultKind = uiSchema['ui:options']?.defaultKind; + const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; const allowArbitraryValues = - uiSchema?.['ui:options']?.allowArbitraryValues ?? true; + uiSchema['ui:options']?.allowArbitraryValues ?? true; const { ownedEntities, loading } = useOwnedEntities(allowedKinds); const entityRefs = ownedEntities?.items From bc6b470825915a73f6dc155ede802afb9ee29082 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 29 Sep 2022 23:32:18 +0200 Subject: [PATCH 113/160] chore: finally make typescript full happy too Signed-off-by: blam --- .../src/next/TemplateWizardPage/Stepper/Stepper.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index 2735ea7e61..f668205573 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -24,7 +24,6 @@ import { } from '@material-ui/core'; import { withTheme } from '@rjsf/core-v5'; import { ErrorSchema, FieldValidation } from '@rjsf/utils'; -import { Theme as MuiTheme } from '@rjsf/material-ui-v5'; import React, { useMemo, useState } from 'react'; import { NextFieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; @@ -53,8 +52,10 @@ export interface StepperProps { extensions: NextFieldExtensionOptions[]; } -// This needs an any for some reason, think it coul -const Form = withTheme(MuiTheme as any); +// TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly +// which is what we're using here as the default types, it needs to depend on @rjsf/core-v5 because +// of the re-writing we're doing. Once we've migrated, we can import this the exact same as before. +const Form = withTheme(require('@rjsf/material-ui-v5').Theme); export const Stepper = (props: StepperProps) => { const { steps } = useTemplateSchema(props.manifest); From 2b825ff357077f5210523ec4505c94d0b381c004 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 29 Sep 2022 23:34:29 +0200 Subject: [PATCH 114/160] chore: bump to latest beta Signed-off-by: blam --- plugins/scaffolder/package.json | 8 +++---- yarn.lock | 40 ++++++++++++++++----------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 6508ae77e7..4508a4b49d 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -55,11 +55,11 @@ "@material-ui/lab": "4.0.0-alpha.57", "@react-hookz/web": "^15.0.0", "@rjsf/core": "^3.2.1", - "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.9", + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.10", "@rjsf/material-ui": "^3.2.1", - "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.9", - "@rjsf/utils": "^5.0.0-beta.9", - "@rjsf/validator-ajv8": "^5.0.0-beta.9", + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.10", + "@rjsf/utils": "^5.0.0-beta.10", + "@rjsf/validator-ajv8": "^5.0.0-beta.10", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", diff --git a/yarn.lock b/yarn.lock index dcecfe2b9f..9af65cfa61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6743,11 +6743,11 @@ __metadata: "@material-ui/lab": 4.0.0-alpha.57 "@react-hookz/web": ^15.0.0 "@rjsf/core": ^3.2.1 - "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.9" + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.10" "@rjsf/material-ui": ^3.2.1 - "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.9" - "@rjsf/utils": ^5.0.0-beta.9 - "@rjsf/validator-ajv8": ^5.0.0-beta.9 + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.10" + "@rjsf/utils": ^5.0.0-beta.10 + "@rjsf/validator-ajv8": ^5.0.0-beta.10 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 "@testing-library/react-hooks": ^8.0.0 @@ -11944,9 +11944,9 @@ __metadata: languageName: node linkType: hard -"@rjsf/core-v5@npm:@rjsf/core@^5.0.0-beta.9": - version: 5.0.0-beta.9 - resolution: "@rjsf/core@npm:5.0.0-beta.9" +"@rjsf/core-v5@npm:@rjsf/core@^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/core@npm:5.0.0-beta.10" dependencies: lodash: ^4.17.15 lodash-es: ^4.17.15 @@ -11955,7 +11955,7 @@ __metadata: peerDependencies: "@rjsf/utils": ^5.0.0-beta.1 react: ^16.14.0 || >=17 - checksum: b55e1a4b3f9f638afdeda1da0abeb145040081201b2f9ed680c93d3dea2ee20e1066da5d2c32c69916d9f2fce422ecb8058f303b9f4df1b20dacf35564f5c7ec + checksum: c3e8852c8fedc6ae23da610033e0bded1bd198cebf7f54a5cdcf3b42d22a3b3864da9c735300cadc7d756f26055a7e62408d51a5ebdb906be9b1bf85bf3712ed languageName: node linkType: hard @@ -11978,16 +11978,16 @@ __metadata: languageName: node linkType: hard -"@rjsf/material-ui-v5@npm:@rjsf/material-ui@^5.0.0-beta.9": - version: 5.0.0-beta.9 - resolution: "@rjsf/material-ui@npm:5.0.0-beta.9" +"@rjsf/material-ui-v5@npm:@rjsf/material-ui@^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/material-ui@npm:5.0.0-beta.10" peerDependencies: "@material-ui/core": ^4.12.3 "@material-ui/icons": ^4.11.2 "@rjsf/core": ^5.0.0-beta.1 "@rjsf/utils": ^5.0.0-beta.1 react: ^16.14.0 || >=17 - checksum: 44624d1aff0f30963d7ae2ab529713d9fcf6e6df809264d6bdee7ef5e71934ab031dbba9dcc9c10be05cb7b0fd679f6fbb1eb27ce7081940b56034a5f2564093 + checksum: c6e602fc331ec37524837123f42e605389c8ea1a9207ed3e9052de380b91fb90365aa962b0020197f0fb2bcbd87baac34bef7dbbb31ea4b57b6587d0fcd81a09 languageName: node linkType: hard @@ -12003,9 +12003,9 @@ __metadata: languageName: node linkType: hard -"@rjsf/utils@npm:^5.0.0-beta.9": - version: 5.0.0-beta.9 - resolution: "@rjsf/utils@npm:5.0.0-beta.9" +"@rjsf/utils@npm:^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/utils@npm:5.0.0-beta.10" dependencies: json-schema-merge-allof: ^0.8.1 jsonpointer: ^5.0.1 @@ -12014,13 +12014,13 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^16.14.0 || >=17 - checksum: ac5b99cf7860c838e5b509a523b19824f4966ec8d44284a026bf81de7c7733bac40432af4c67ab538bd400f529dc55d9001e07618a514cac365cd604ff786b11 + checksum: df5dda5a17ce11df2342ceff8d6ad2b3dad3a1f98d44aa7da378e6cd48387e4af023964b6143836e1ad16ff68f6592c0dcf24ff9f3fc859480b42b2c879406bc languageName: node linkType: hard -"@rjsf/validator-ajv8@npm:^5.0.0-beta.9": - version: 5.0.0-beta.9 - resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.9" +"@rjsf/validator-ajv8@npm:^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.10" dependencies: ajv: ^8.11.0 ajv-formats: ^2.1.1 @@ -12028,7 +12028,7 @@ __metadata: lodash-es: ^4.17.15 peerDependencies: "@rjsf/utils": ^5.0.0-beta.1 - checksum: 64ca81f1f440b08f959d61e1f97b25d890cd8f03582287004961d0ba0ec7d6719748b00d821ef009d330816ca2e4e030d46fa695a3c12c338518e469242694cd + checksum: faa359c9ec58cf0fff35932d4e08a5c42f8449120cae606ebff108814f974e8db8a62df85e90e1fec7b0345388df09b83359ae1cc09103d41e73f298472b846a languageName: node linkType: hard From 1047baa92657a2d29c763924355bbfa522df8f1b Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Oct 2022 15:01:20 +0200 Subject: [PATCH 115/160] chore: added changeset for the scaffolder package Signed-off-by: blam --- .changeset/lucky-beans-bathe.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lucky-beans-bathe.md diff --git a/.changeset/lucky-beans-bathe.md b/.changeset/lucky-beans-bathe.md new file mode 100644 index 0000000000..b425a29599 --- /dev/null +++ b/.changeset/lucky-beans-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports From 8020d8e8e938873c9cb22aad0139a800e92d6109 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Oct 2022 15:07:10 +0200 Subject: [PATCH 116/160] chore: export some more things out of the alpha exports Signed-off-by: blam --- plugins/scaffolder/api-report.md | 44 ++++++++++++++++++++++++++++++++ plugins/scaffolder/src/index.ts | 8 +++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 23b374541b..824b56daf0 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -15,7 +15,9 @@ import { Extension } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; import { FieldProps } from '@rjsf/core'; +import { FieldProps as FieldProps_2 } from '@rjsf/utils'; import { FieldValidation } from '@rjsf/core'; +import { FieldValidation as FieldValidation_2 } from '@rjsf/utils'; import type { FormProps } from '@rjsf/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -29,6 +31,16 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { UIOptionsType } from '@rjsf/utils'; +import { UiSchema } from '@rjsf/utils'; + +// @alpha +export function createNextScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps extends UIOptionsType = {}, +>( + options: FieldExtensionOptions, +): Extension>; // @public export function createScaffolderFieldExtension< @@ -155,6 +167,38 @@ export type LogEvent = { taskId: string; }; +// @alpha +export type NextCustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidation_2, + context: { + apiHolder: ApiHolder; + }, +) => void | Promise; + +// @alpha +export interface NextFieldExtensionComponentProps< + TFieldReturnValue, + TUiOptions = {}, +> extends PropsWithChildren> { + // (undocumented) + uiSchema?: UiSchema & { + 'ui:options'?: TUiOptions & UIOptionsType; + }; +} + +// @alpha +export type NextFieldExtensionOptions< + TFieldReturnValue = unknown, + TInputProps = unknown, +> = { + name: string; + component: ( + props: NextFieldExtensionComponentProps, + ) => JSX.Element | null; + validation?: NextCustomFieldValidator; +}; + // @alpha export type NextRouterProps = { components?: { diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index c16e12beb5..e68eece8e5 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -59,11 +59,17 @@ export { RepoUrlPickerFieldExtension, ScaffolderPage, scaffolderPlugin, - NextScaffolderPage, } from './plugin'; export * from './components'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ +export { NextScaffolderPage } from './plugin'; export type { NextRouterProps } from './next'; export type { TemplateGroupFilter } from './next'; +export { + createNextScaffolderFieldExtension, + type NextCustomFieldValidator, + type NextFieldExtensionOptions, + type NextFieldExtensionComponentProps, +} from './extensions'; From 42a8b0c3f3648d3e75e7fe81558bf2f021124e70 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Oct 2022 15:12:31 +0200 Subject: [PATCH 117/160] chore: fix the typings Signed-off-by: blam Signed-off-by: blam --- plugins/scaffolder/src/extensions/index.tsx | 2 +- plugins/scaffolder/src/next/Router/Router.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 38cabd5511..08075b5b9d 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -71,7 +71,7 @@ export function createNextScaffolderFieldExtension< TReturnValue = unknown, TInputProps extends UIOptionsType = {}, >( - options: FieldExtensionOptions, + options: NextFieldExtensionOptions, ): Extension> { return { expose() { diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index f0e5823f84..608709b05c 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -73,7 +73,7 @@ export const Router = (props: PropsWithChildren) => { customFieldExtension => customFieldExtension.name === name, ), ), - ] as NextFieldExtensionOptions[]; // here just to coerce the default extensions to the correct type + ] as NextFieldExtensionOptions[]; return ( From 2d76e6f6c473721ad00c0dfb29aa479d7c0fce0f Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Oct 2022 17:36:41 +0200 Subject: [PATCH 118/160] chore: fixing api-reports Signed-off-by: blam --- plugins/scaffolder/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 824b56daf0..cf69736bd4 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -39,7 +39,7 @@ export function createNextScaffolderFieldExtension< TReturnValue = unknown, TInputProps extends UIOptionsType = {}, >( - options: FieldExtensionOptions, + options: NextFieldExtensionOptions, ): Extension>; // @public From 6edbf33499bfef87b0bfcd7c410bb1a0c85148f2 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 11 Oct 2022 11:26:35 +0100 Subject: [PATCH 119/160] chore: code reviews Signed-off-by: blam --- plugins/scaffolder/src/next/Router/Router.tsx | 3 ++- .../src/next/TemplateWizardPage/Stepper/Stepper.tsx | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 608709b05c..125a0bbbf6 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -22,6 +22,7 @@ import { FIELD_EXTENSION_KEY, DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, NextFieldExtensionOptions, + FieldExtensionOptions, } from '../../extensions'; import { useElementFilter } from '@backstage/core-plugin-api'; @@ -60,7 +61,7 @@ export const Router = (props: PropsWithChildren) => { .selectByComponentData({ key: FIELD_EXTENSION_WRAPPER_KEY, }) - .findComponentData({ + .findComponentData({ key: FIELD_EXTENSION_KEY, }), ); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index f668205573..97f21111d3 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -96,9 +96,9 @@ export const Stepper = (props: StepperProps) => { const returnedValidation = await validation(formData); - const hasErrors = Object.values(returnedValidation).some(i => { - return i.__errors?.length! > 0 ?? false; - }); + const hasErrors = Object.values(returnedValidation).some( + i => i.__errors?.length, + ); if (hasErrors) { setErrors(returnedValidation); From 4497bd4e4859ce39195f27e46cc8b4253ce878c4 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Oct 2022 21:55:11 +0200 Subject: [PATCH 120/160] chore: starting to wire together a TaskPage for the router Signed-off-by: blam --- plugins/scaffolder/src/next/Router/Router.tsx | 13 +++++- .../scaffolder/src/next/TaskPage/TaskPage.tsx | 33 +++++++++++++++ plugins/scaffolder/src/next/TaskPage/index.ts | 16 ++++++++ .../Stepper/Stepper.test.tsx | 7 ++-- .../TemplateWizardPage/Stepper/Stepper.tsx | 15 +++---- .../TemplateWizardPage/TemplateWizardPage.tsx | 41 ++++++++++++++----- plugins/scaffolder/src/routes.ts | 6 +++ 7 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 plugins/scaffolder/src/next/TaskPage/TaskPage.tsx create mode 100644 plugins/scaffolder/src/next/TaskPage/index.ts diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 125a0bbbf6..c83e1a046a 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -28,8 +28,13 @@ import { import { useElementFilter } from '@backstage/core-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups'; -import { nextSelectedTemplateRouteRef } from '../../routes'; +import { + nextScaffolderTaskRouteRef, + nextSelectedTemplateRouteRef, +} from '../../routes'; import { SecretsContextProvider } from '../../components/secrets/SecretsContext'; +import { TaskPage } from '../TaskPage'; +import { ErrorPage } from '@backstage/core-components'; /** * The Props for the Scaffolder Router @@ -87,7 +92,6 @@ export const Router = (props: PropsWithChildren) => { /> } /> - ) => { } /> + } /> + } + /> ); }; diff --git a/plugins/scaffolder/src/next/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/next/TaskPage/TaskPage.tsx new file mode 100644 index 0000000000..ff0f4cd820 --- /dev/null +++ b/plugins/scaffolder/src/next/TaskPage/TaskPage.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Page, Header, Content } from '@backstage/core-components'; + +export const TaskPage = () => { + return ( + +
+ +

Hey

+
+ + ); +}; diff --git a/plugins/scaffolder/src/next/TaskPage/index.ts b/plugins/scaffolder/src/next/TaskPage/index.ts new file mode 100644 index 0000000000..b129601766 --- /dev/null +++ b/plugins/scaffolder/src/next/TaskPage/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { TaskPage } from './TaskPage'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index f605e9c114..12ad05ab75 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -30,7 +30,7 @@ describe('Stepper', () => { }; const { getByText } = await renderInTestApp( - , + , ); for (const step of manifest.steps) { @@ -48,7 +48,7 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - , + , ); expect(getByRole('button', { name: 'Next' })).toBeInTheDocument(); @@ -88,7 +88,7 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - , + , ); await fireEvent.change(getByRole('textbox', { name: 'name' }), { @@ -134,6 +134,7 @@ describe('Stepper', () => { , ); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index 97f21111d3..fe055433b4 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApiHolder } from '@backstage/core-plugin-api'; -import { JsonObject } from '@backstage/types'; +import { useApi, useApiHolder } from '@backstage/core-plugin-api'; +import { JsonObject, JsonValue } from '@backstage/types'; import { Stepper as MuiStepper, Step as MuiStep, @@ -31,6 +31,7 @@ import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; import validator from '@rjsf/validator-ajv8'; +import { scaffolderApiRef } from '../../../api'; const useStyles = makeStyles(theme => ({ backButton: { @@ -50,6 +51,7 @@ const useStyles = makeStyles(theme => ({ export interface StepperProps { manifest: TemplateParameterSchema; extensions: NextFieldExtensionOptions[]; + onComplete: (values: Record) => Promise; } // TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly @@ -109,10 +111,6 @@ export const Stepper = (props: StepperProps) => { setFormState(current => ({ ...current, ...formData })); }; - const handleCreate = () => { - // TODO(blam): Create the template in a modal with the ability to view the logs etc. - }; - return ( <> @@ -161,7 +159,10 @@ export const Stepper = (props: StepperProps) => { > Back -
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index f6739e6389..f9dc97f32b 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Page, Header, @@ -23,7 +23,7 @@ import { MarkdownContent, } from '@backstage/core-components'; import { NextFieldExtensionOptions } from '../../extensions'; -import { Navigate } from 'react-router'; +import { Navigate, useNavigate } from 'react-router'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { errorApiRef, @@ -36,7 +36,13 @@ import useAsync from 'react-use/lib/useAsync'; import { makeStyles } from '@material-ui/core'; import { Stepper } from './Stepper'; import { BackstageTheme } from '@backstage/theme'; -import { nextRouteRef, selectedTemplateRouteRef } from '../../routes'; +import { + nextRouteRef, + nextScaffolderTaskRouteRef, + selectedTemplateRouteRef, +} from '../../routes'; +import { SecretsContext } from '../../components/secrets/SecretsContext'; +import { JsonValue } from '@backstage/types'; export interface TemplateWizardPageProps { customFieldExtensions: NextFieldExtensionOptions[]; @@ -67,17 +73,31 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const styles = useStyles(); const rootRef = useRouteRef(nextRouteRef); + const nextTemplateRef = useRouteRef(nextScaffolderTaskRouteRef); + const { secrets } = useContext(SecretsContext) ?? {}; + const scaffolderApi = useApi(scaffolderApiRef); + const navigate = useNavigate(); const { templateName, namespace } = useRouteRefParams( selectedTemplateRouteRef, ); + const templateRef = stringifyEntityRef({ + kind: 'Template', + namespace, + name: templateName, + }); + const errorApi = useApi(errorApiRef); - const { loading, manifest, error } = useTemplateParameterSchema( - stringifyEntityRef({ - kind: 'Template', - namespace, - name: templateName, - }), - ); + const { loading, manifest, error } = useTemplateParameterSchema(templateRef); + + const onComplete = async (values: Record) => { + const { taskId } = await scaffolderApi.scaffold({ + templateRef, + values, + secrets, + }); + + navigate(nextTemplateRef({ taskId })); + }; useEffect(() => { if (error) { @@ -113,6 +133,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { )} diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 2ef6583b08..5e2d191443 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -65,6 +65,12 @@ export const scaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); +export const nextScaffolderTaskRouteRef = createSubRouteRef({ + id: 'scaffolder/next/task', + parent: nextRouteRef, + path: '/tasks/:taskId', +}); + export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, From bd42442fa50d26aa1b0fa47fef42463ac2045407 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 9 Oct 2022 15:20:43 +0200 Subject: [PATCH 121/160] chore: re-use the old task page for now as we want to ship something early Signed-off-by: blam --- .../src/next/TemplateWizardPage/TemplateWizardPage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index f9dc97f32b..f545bc71b4 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -39,6 +39,7 @@ import { BackstageTheme } from '@backstage/theme'; import { nextRouteRef, nextScaffolderTaskRouteRef, + scaffolderTaskRouteRef, selectedTemplateRouteRef, } from '../../routes'; import { SecretsContext } from '../../components/secrets/SecretsContext'; @@ -73,13 +74,14 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const styles = useStyles(); const rootRef = useRouteRef(nextRouteRef); - const nextTemplateRef = useRouteRef(nextScaffolderTaskRouteRef); + const taskRoute = useRouteRef(scaffolderTaskRouteRef); const { secrets } = useContext(SecretsContext) ?? {}; const scaffolderApi = useApi(scaffolderApiRef); const navigate = useNavigate(); const { templateName, namespace } = useRouteRefParams( selectedTemplateRouteRef, ); + const templateRef = stringifyEntityRef({ kind: 'Template', namespace, @@ -96,7 +98,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { secrets, }); - navigate(nextTemplateRef({ taskId })); + navigate(taskRoute({ taskId })); }; useEffect(() => { From 92e490d6b4165e7b5a3c5dbd608be2f74e0af5f0 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 9 Oct 2022 15:24:37 +0200 Subject: [PATCH 122/160] chore: added changeset Signed-off-by: blam --- .changeset/cuddly-ways-fail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cuddly-ways-fail.md diff --git a/.changeset/cuddly-ways-fail.md b/.changeset/cuddly-ways-fail.md new file mode 100644 index 0000000000..bb1b356036 --- /dev/null +++ b/.changeset/cuddly-ways-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Make the `/next` scaffolder work end to end with the old `TaskPage` view From 698e213f8ca5d1d215909339b030f82c87e306e8 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Oct 2022 14:45:00 +0200 Subject: [PATCH 123/160] chore: fixing typescript issues and getting this PR ready to ship! Signed-off-by: blam --- .../scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx | 3 +-- .../src/next/TemplateWizardPage/TemplateWizardPage.tsx | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index fe055433b4..511b020c3c 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApi, useApiHolder } from '@backstage/core-plugin-api'; +import { useApiHolder } from '@backstage/core-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; import { Stepper as MuiStepper, @@ -31,7 +31,6 @@ import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; import validator from '@rjsf/validator-ajv8'; -import { scaffolderApiRef } from '../../../api'; const useStyles = makeStyles(theme => ({ backButton: { diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index f545bc71b4..b650e4a676 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -38,7 +38,6 @@ import { Stepper } from './Stepper'; import { BackstageTheme } from '@backstage/theme'; import { nextRouteRef, - nextScaffolderTaskRouteRef, scaffolderTaskRouteRef, selectedTemplateRouteRef, } from '../../routes'; From deb5da85d5647b272b797c416aa608c6174c2720 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 11 Oct 2022 11:30:09 +0100 Subject: [PATCH 124/160] chore: fixing code review comments Signed-off-by: blam --- plugins/scaffolder/src/next/Router/Router.tsx | 12 +------ .../scaffolder/src/next/TaskPage/TaskPage.tsx | 33 ------------------- plugins/scaffolder/src/next/TaskPage/index.ts | 16 --------- 3 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 plugins/scaffolder/src/next/TaskPage/TaskPage.tsx delete mode 100644 plugins/scaffolder/src/next/TaskPage/index.ts diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index c83e1a046a..ebe00fdfb8 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -28,13 +28,8 @@ import { import { useElementFilter } from '@backstage/core-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups'; -import { - nextScaffolderTaskRouteRef, - nextSelectedTemplateRouteRef, -} from '../../routes'; +import { nextSelectedTemplateRouteRef } from '../../routes'; import { SecretsContextProvider } from '../../components/secrets/SecretsContext'; -import { TaskPage } from '../TaskPage'; -import { ErrorPage } from '@backstage/core-components'; /** * The Props for the Scaffolder Router @@ -100,11 +95,6 @@ export const Router = (props: PropsWithChildren) => { } /> - } /> - } - /> ); }; diff --git a/plugins/scaffolder/src/next/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/next/TaskPage/TaskPage.tsx deleted file mode 100644 index ff0f4cd820..0000000000 --- a/plugins/scaffolder/src/next/TaskPage/TaskPage.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 React from 'react'; -import { Page, Header, Content } from '@backstage/core-components'; - -export const TaskPage = () => { - return ( - -
- -

Hey

-
- - ); -}; diff --git a/plugins/scaffolder/src/next/TaskPage/index.ts b/plugins/scaffolder/src/next/TaskPage/index.ts deleted file mode 100644 index b129601766..0000000000 --- a/plugins/scaffolder/src/next/TaskPage/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { TaskPage } from './TaskPage'; From bbbe968e10c6f08beef90a84102f13e218e706f1 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 11 Oct 2022 11:46:28 +0100 Subject: [PATCH 125/160] Fixed allowing optional params outside of the toQuery and apply Signed-off-by: Harry Hogg Co-authored-by: Mike Lewis --- .../createConditionExports.test.ts | 8 +++--- plugins/permission-node/src/types.ts | 27 +++---------------- 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index 424b3c3c24..dad57e2b8b 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -46,7 +46,7 @@ const testIntegration = () => description: 'Test rule 2', resourceType: 'test-resource', paramsSchema: z.object({ - foo: z.string(), + foo: z.string().optional(), }), apply: (_resource: any) => false, toQuery: params => ({ @@ -76,12 +76,10 @@ describe('createConditionExports', () => { }, }); - expect(conditions.testRule2({ foo: 'baz' })).toEqual({ + expect(conditions.testRule2({})).toEqual({ rule: 'testRule2', resourceType: 'test-resource', - params: { - foo: 'baz', - }, + params: {}, }); }); }); diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 6b15df407e..36e30714f6 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -21,22 +21,6 @@ import type { import { z } from 'zod'; import { NoInfer } from './integration/util'; -/** - * A ZodSchema that reflects the structure of the parameters that are passed to - * into a {@link PermissionRule}. - * - * @public - */ -export type PermissionRuleSchema = z.ZodObject<{ - // Parameters can be optional, however we we want to make sure that the - // parameters are always present in the schema, even if they are undefined. - // We remove the optional flag from the schema, and then add it back in - // with an optional zod type. - [P in keyof TParams]-?: TParams[P] extends undefined - ? z.ZodOptionalType> - : z.ZodType; -}>; - /** * A conditional rule that can be provided in an * {@link @backstage/permission-common#AuthorizeDecision} response to an authorization request. @@ -66,24 +50,19 @@ export type PermissionRule< /** * A ZodSchema that reflects the structure of the parameters that are passed to */ - paramsSchema?: PermissionRuleSchema; + paramsSchema?: z.ZodSchema; /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply( - resource: TResource, - params: NoInfer>>, - ): boolean; + apply(resource: TResource, params: NoInfer): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery( - params: NoInfer>>, - ): PermissionCriteria; + toQuery(params: NoInfer): PermissionCriteria; }; From 30592654f9148eae63978cf39938bd7128369d8f Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 11 Oct 2022 11:51:43 +0100 Subject: [PATCH 126/160] Added link to Zod in changelog Signed-off-by: Harry Hogg --- .changeset/kind-bees-suffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/kind-bees-suffer.md b/.changeset/kind-bees-suffer.md index bdd6d1f594..79e9c0a027 100644 --- a/.changeset/kind-bees-suffer.md +++ b/.changeset/kind-bees-suffer.md @@ -3,7 +3,7 @@ '@backstage/plugin-permission-node': minor --- -**BREAKING**: When defining permission rules, it's now necessary to provide a ZodSchema that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. +**BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... From 3f25d863b0adb9b2036339cdfe031559cc1a6235 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 11 Oct 2022 11:55:40 +0100 Subject: [PATCH 127/160] Updated changelog for plugin-catalog-backend to be more focused on createCatalogConditionalDecision Signed-off-by: Harry Hogg --- .changeset/seven-panthers-chew.md | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/.changeset/seven-panthers-chew.md b/.changeset/seven-panthers-chew.md index 7b44f2f633..33c9166d41 100644 --- a/.changeset/seven-panthers-chew.md +++ b/.changeset/seven-panthers-chew.md @@ -2,32 +2,4 @@ '@backstage/plugin-catalog-backend': minor --- -**BREAKING** Due to the changes made in the Permission framework. The catalogs permission rules and the API of `createCatalogPermissionRule` have been changed to reflect the change from individual function parameters to a single object parameter and the addition of the `paramsSchema`. - -As an example for the `hasLabel` rule. The API before the change was - -```ts -hasLabel.apply(entity, 'backstage.io/testLabel'); -hasLabel.toQuery('backstage.io/testLabel'); -``` - -and the API after the change now is - -```ts -hasLabel.apply(entity, { - label: 'backstage.io/testLabel', -}); - -hasLabel.toQuery({ - label: 'backstage.io/testLabel', -}); -``` - -This applies to all of the permission rules exported by the catalog backend. - -- `hasAnnotation` -- `hasLabel` -- `hasMetadata` -- `hasSpec` -- `isEntityKind` -- `isEntityOwner` +**BREAKING** The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the PermissionRule type. From 04db0e8afbfd84157fc7a4ffc848d431e7d75679 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Tue, 11 Oct 2022 12:32:26 +0100 Subject: [PATCH 128/160] Updated API Reports Signed-off-by: Harry Hogg --- plugins/catalog-backend/api-report.md | 12 ++++++------ plugins/permission-node/api-report.md | 18 +++--------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 6935243cf5..c9402945c1 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -179,8 +179,8 @@ export const catalogConditions: Conditions<{ EntitiesSearchFilter, 'catalog-entity', { + value?: string | undefined; annotation: string; - value: string | undefined; } >; hasLabel: PermissionRule< @@ -196,8 +196,8 @@ export const catalogConditions: Conditions<{ EntitiesSearchFilter, 'catalog-entity', { + value?: string | undefined; key: string; - value: string | undefined; } >; hasSpec: PermissionRule< @@ -205,8 +205,8 @@ export const catalogConditions: Conditions<{ EntitiesSearchFilter, 'catalog-entity', { + value?: string | undefined; key: string; - value: string | undefined; } >; isEntityKind: PermissionRule< @@ -468,8 +468,8 @@ export const permissionRules: { EntitiesSearchFilter, 'catalog-entity', { + value?: string | undefined; annotation: string; - value: string | undefined; } >; hasLabel: PermissionRule< @@ -485,8 +485,8 @@ export const permissionRules: { EntitiesSearchFilter, 'catalog-entity', { + value?: string | undefined; key: string; - value: string | undefined; } >; hasSpec: PermissionRule< @@ -494,8 +494,8 @@ export const permissionRules: { EntitiesSearchFilter, 'catalog-entity', { + value?: string | undefined; key: string; - value: string | undefined; } >; isEntityKind: PermissionRule< diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 3895b4b3cb..7feb20d7aa 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -179,23 +179,11 @@ export type PermissionRule< name: string; description: string; resourceType: TResourceType; - paramsSchema?: PermissionRuleSchema; - apply( - resource: TResource, - params: NoInfer>>, - ): boolean; - toQuery( - params: NoInfer>>, - ): PermissionCriteria; + paramsSchema?: z.ZodSchema; + apply(resource: TResource, params: NoInfer): boolean; + toQuery(params: NoInfer): PermissionCriteria; }; -// @public -export type PermissionRuleSchema = z.ZodObject<{ - [P in keyof TParams]-?: TParams[P] extends undefined - ? z.ZodOptionalType> - : z.ZodType; -}>; - // @public export type PolicyQuery = { permission: Permission; From 632420a0a5921800fb158fe6e9b002b2526bbf70 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Fri, 7 Oct 2022 12:19:55 -0400 Subject: [PATCH 129/160] Use RUN --mount=type=cache,... in example backend dockerfile Signed-off-by: Boris Bera --- packages/backend/Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 21ff07deb4..8e0d711cd3 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -13,9 +13,10 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. @@ -38,7 +39,8 @@ COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz # Note that this install is not immutable, which is one of the reasons we don't recommend Yarn 3 yet -RUN yarn workspaces focus --all --production && rm -rf "$(yarn cache clean)" +RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.yarn/berry/cache,sharing=locked \ + yarn workspaces focus --all --production # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ From 74801d277cafbeffb121211c2ff14d185465d424 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Tue, 11 Oct 2022 05:47:12 -0400 Subject: [PATCH 130/160] Use RUN --mount in Host build dockerfile Signed-off-by: Boris Bera --- docs/deployment/docker.md | 8 +++++--- .../templates/default-app/packages/backend/Dockerfile | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index d35e7dd593..f809b3794b 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -61,9 +61,10 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. @@ -79,7 +80,8 @@ ENV NODE_ENV production COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 300000 # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 682798b826..3e9ca5ba48 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -13,9 +13,10 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. @@ -31,7 +32,8 @@ ENV NODE_ENV production COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 300000 # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ From 463aa5422b7808ff21403b4b2a0012d597e378f8 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Tue, 11 Oct 2022 06:50:16 -0400 Subject: [PATCH 131/160] Optimize the multi-stage build docker image Signed-off-by: Boris Bera --- docs/deployment/docker.md | 44 +++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index f809b3794b..1cd351c2bc 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -165,53 +165,64 @@ RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf { # Stage 2 - Install dependencies and build packages FROM node:16-bullseye-slim AS build -WORKDIR /app -COPY --from=packages /app . - # install sqlite3 dependencies -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ yarn config set python /usr/bin/python3 -RUN yarn install --frozen-lockfile --network-timeout 600000 && rm -rf "$(yarn cache dir)" +USER node +WORKDIR /app -COPY . . +COPY --from=packages --chown=node:node /app . + +# Stop cypress from downloading it's massive binary. +ENV CYPRESS_INSTALL_BINARY=0 +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --network-timeout 600000 + +COPY --chown=node:node . . RUN yarn tsc RUN yarn --cwd packages/backend build # If you have not yet migrated to package roles, use the following command instead: # RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies +RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ + && tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \ + && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle + # Stage 3 - Build the actual backend image and install production dependencies FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node WORKDIR /app -# This switches many Node.js dependencies to production mode. -ENV NODE_ENV production - # Copy the install dependencies from the build stage and context -COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./ -RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./ -RUN yarn install --frozen-lockfile --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 600000 # Copy the built packages from the build stage -COPY --from=build --chown=node:node /app/packages/backend/dist/bundle.tar.gz . -RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ # Copy any other files that we need at runtime COPY --chown=node:node app-config.yaml ./ +# This switches many Node.js dependencies to production mode. +ENV NODE_ENV production + CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` @@ -227,6 +238,7 @@ however ignore any existing build output or dependencies on the host. For our new `.dockerignore`, replace the contents of your existing one with this: ```text +dist-types node_modules packages/*/dist packages/*/node_modules From f87680543e592e50bb60cd5bba20b623270a7567 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Tue, 11 Oct 2022 06:51:43 -0400 Subject: [PATCH 132/160] Set uid= & gid= at the end of RUN --mount This is strictly to match up with the rest of those statements in the codebase Signed-off-by: Boris Bera --- packages/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 8e0d711cd3..60c6ffd27f 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -39,7 +39,7 @@ COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz # Note that this install is not immutable, which is one of the reasons we don't recommend Yarn 3 yet -RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.yarn/berry/cache,sharing=locked \ +RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,sharing=locked,uid=1000,gid=1000 \ yarn workspaces focus --all --production # Then copy the rest of the backend bundle, along with any other files we might want. From 01dff06be456c0b01d22f98befa0725826875936 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Tue, 11 Oct 2022 07:47:48 -0400 Subject: [PATCH 133/160] Add changeset for cache mounts in dockerfile Signed-off-by: Boris Bera --- .changeset/short-plants-return.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/short-plants-return.md diff --git a/.changeset/short-plants-return.md b/.changeset/short-plants-return.md new file mode 100644 index 0000000000..3afdc81ceb --- /dev/null +++ b/.changeset/short-plants-return.md @@ -0,0 +1,6 @@ +--- +'example-backend': patch +'@backstage/create-app': patch +--- + +Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. From 2a6d7625398854fefec1222e0b038a086e5d7f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 11 Oct 2022 13:10:48 +0100 Subject: [PATCH 134/160] just some vale fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .github/vale/Vocab/Backstage/accept.txt | 9 +++++++ .../src/routing/SubRouteRef.ts | 2 +- .../src/example-docs/docs/index.md | 24 +++++++++---------- .../documented-component/docs/extensions.md | 24 +++++++++---------- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index ae94912f38..e40dfffda1 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -2,6 +2,7 @@ abc accessors addon addons +ADRs Airbrake Airbrakes airbrake @@ -28,6 +29,7 @@ bool boolean builtins callout +CDNs Chai changeset changesets @@ -129,6 +131,7 @@ hotspots http https Iain +iLert img incentivised Indal @@ -139,6 +142,7 @@ JaCoCo JavaScript jenkins Jira +JWTs jq js json @@ -147,6 +151,7 @@ jsx Kaewkasi Keyv Knex +KPIs kubectl kubernetes kubernetes @@ -308,6 +313,7 @@ Superfences superset supertype storable +SVGs talkdesk Talkdesk tasklist @@ -352,12 +358,15 @@ utils validator validators varchar +VPCs +VSCode Wayfair Weaveworks Webpack winston www WWW +XCMetrics XML xyz yaml diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index 61d89fcbdb..14a614c2ba 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -72,7 +72,7 @@ export type ParamNames = export type PathParams = { [name in ParamNames]: string }; /** - * Merges a param object type with with an optional params type into a params object. + * Merges a param object type with an optional params type into a params object. * @public */ export type MergeParams< diff --git a/packages/techdocs-cli/src/example-docs/docs/index.md b/packages/techdocs-cli/src/example-docs/docs/index.md index 074d3ccb3e..66e4c96b2f 100644 --- a/packages/techdocs-cli/src/example-docs/docs/index.md +++ b/packages/techdocs-cli/src/example-docs/docs/index.md @@ -73,20 +73,20 @@ const addThingToThing = (a, b) a + b; # MDX truly sane lists -- attributes +- `attributes` -- customer - - first_name - - test - - family_name - - email -- person - - first_name - - family_name - - birth_date -- subscription_id +- `customer` + - `first_name` + - `test` + - `family_name` + - `email` +- `person` + - `first_name` + - `family_name` + - `birth_date` +- `subscription_id` -- request +- `request` *[MOCDOC]: Mock Documentation diff --git a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md index 0fda26302d..eb9abca74a 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md @@ -96,17 +96,17 @@ Animals: :tiger: :horse: :turtle: :wolf: :frog: ### MDX truly sane lists -- attributes +- `attributes` -- customer - - first_name - - test - - family_name - - email -- person - - first_name - - family_name - - birth_date -- subscription_id +- `customer` + - `first_name` + - `test` + - `family_name` + - `email` +- `person` + - `first_name` + - `family_name` + - `birth_date` +- `subscription_id` -- request +- `request` From cd6c66ef9c37933d32d6aa49e0e3f90e9172f0c8 Mon Sep 17 00:00:00 2001 From: Harrison Hogg <7130591+HHogg@users.noreply.github.com> Date: Tue, 11 Oct 2022 14:11:12 +0100 Subject: [PATCH 135/160] Update .changeset/seven-panthers-chew.md Co-authored-by: Patrik Oldsberg Signed-off-by: Harrison Hogg <7130591+HHogg@users.noreply.github.com> --- .changeset/seven-panthers-chew.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/seven-panthers-chew.md b/.changeset/seven-panthers-chew.md index 33c9166d41..110df1d389 100644 --- a/.changeset/seven-panthers-chew.md +++ b/.changeset/seven-panthers-chew.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': minor --- -**BREAKING** The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the PermissionRule type. +The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` From 44c52b298ceff115fb0321193ac02260058f986a Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Tue, 11 Oct 2022 09:47:45 -0400 Subject: [PATCH 136/160] Remove un-neccessary changeset for example-backend Signed-off-by: Boris Bera --- .changeset/short-plants-return.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/short-plants-return.md b/.changeset/short-plants-return.md index 3afdc81ceb..0a4974c38a 100644 --- a/.changeset/short-plants-return.md +++ b/.changeset/short-plants-return.md @@ -1,5 +1,4 @@ --- -'example-backend': patch '@backstage/create-app': patch --- From 9282c018c7c6c071e31e5124c40ebe140dc1152e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 11 Oct 2022 14:30:05 +0000 Subject: [PATCH 137/160] Version Packages (next) --- .changeset/pre.json | 31 +- docs/releases/v1.7.0-next.2-changelog.md | 2127 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 11 + packages/app-defaults/package.json | 2 +- packages/app/CHANGELOG.md | 62 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 11 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 14 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 8 + packages/backend-defaults/package.json | 2 +- packages/backend-next/CHANGELOG.md | 10 + packages/backend-next/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 10 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 11 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 11 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 42 + packages/backend/package.json | 2 +- packages/catalog-client/CHANGELOG.md | 8 + packages/catalog-client/package.json | 2 +- packages/catalog-model/CHANGELOG.md | 9 + packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 13 + packages/cli/package.json | 2 +- packages/codemods/CHANGELOG.md | 7 + packages/codemods/package.json | 2 +- packages/config-loader/CHANGELOG.md | 10 + packages/config-loader/package.json | 2 +- packages/config/CHANGELOG.md | 7 + packages/config/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 10 + packages/core-app-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 11 + packages/core-components/package.json | 2 +- packages/core-plugin-api/CHANGELOG.md | 9 + packages/core-plugin-api/package.json | 2 +- packages/create-app/CHANGELOG.md | 8 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 15 + packages/dev-utils/package.json | 2 +- packages/errors/CHANGELOG.md | 7 + packages/errors/package.json | 2 +- packages/integration-react/CHANGELOG.md | 11 + packages/integration-react/package.json | 2 +- packages/integration/CHANGELOG.md | 8 + packages/integration/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 19 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 11 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 13 + packages/test-utils/package.json | 2 +- plugins/adr-backend/CHANGELOG.md | 14 + plugins/adr-backend/package.json | 2 +- plugins/adr-common/CHANGELOG.md | 9 + plugins/adr-common/package.json | 2 +- plugins/adr/CHANGELOG.md | 15 + plugins/adr/package.json | 2 +- plugins/airbrake-backend/CHANGELOG.md | 9 + plugins/airbrake-backend/package.json | 2 +- plugins/airbrake/CHANGELOG.md | 13 + plugins/airbrake/package.json | 2 +- plugins/allure/CHANGELOG.md | 11 + plugins/allure/package.json | 2 +- plugins/analytics-module-ga/CHANGELOG.md | 10 + plugins/analytics-module-ga/package.json | 2 +- plugins/apache-airflow/CHANGELOG.md | 8 + plugins/apache-airflow/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 12 + plugins/api-docs/package.json | 2 +- plugins/apollo-explorer/CHANGELOG.md | 9 + plugins/apollo-explorer/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 11 + plugins/app-backend/package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 13 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 9 + plugins/auth-node/package.json | 2 +- plugins/azure-devops-backend/CHANGELOG.md | 9 + plugins/azure-devops-backend/package.json | 2 +- plugins/azure-devops/CHANGELOG.md | 13 + plugins/azure-devops/package.json | 2 +- plugins/badges-backend/CHANGELOG.md | 12 + plugins/badges-backend/package.json | 2 +- plugins/badges/CHANGELOG.md | 12 + plugins/badges/package.json | 2 +- plugins/bazaar-backend/CHANGELOG.md | 37 + plugins/bazaar-backend/package.json | 2 +- plugins/bazaar/CHANGELOG.md | 16 + plugins/bazaar/package.json | 2 +- plugins/bitbucket-cloud-common/CHANGELOG.md | 7 + plugins/bitbucket-cloud-common/package.json | 2 +- plugins/bitrise/CHANGELOG.md | 11 + plugins/bitrise/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 14 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 14 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 19 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 19 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 19 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 12 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 35 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-common/CHANGELOG.md | 17 + plugins/catalog-common/package.json | 2 +- plugins/catalog-customized/CHANGELOG.md | 8 + plugins/catalog-customized/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 12 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-graphql/CHANGELOG.md | 9 + plugins/catalog-graphql/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 36 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 16 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 18 + plugins/catalog-react/package.json | 2 +- plugins/catalog/CHANGELOG.md | 18 + plugins/catalog/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/cicd-statistics/CHANGELOG.md | 9 + plugins/cicd-statistics/package.json | 2 +- plugins/circleci/CHANGELOG.md | 11 + plugins/circleci/package.json | 2 +- plugins/cloudbuild/CHANGELOG.md | 11 + plugins/cloudbuild/package.json | 2 +- plugins/code-climate/CHANGELOG.md | 11 + plugins/code-climate/package.json | 2 +- plugins/code-coverage-backend/CHANGELOG.md | 12 + plugins/code-coverage-backend/package.json | 2 +- plugins/code-coverage/CHANGELOG.md | 13 + plugins/code-coverage/package.json | 2 +- plugins/codescene/CHANGELOG.md | 11 + plugins/codescene/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 12 + plugins/config-schema/package.json | 2 +- plugins/cost-insights/CHANGELOG.md | 12 + plugins/cost-insights/package.json | 2 +- plugins/dynatrace/CHANGELOG.md | 11 + plugins/dynatrace/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 10 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list-common/CHANGELOG.md | 7 + plugins/example-todo-list-common/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 9 + plugins/example-todo-list/package.json | 2 +- plugins/explore-react/CHANGELOG.md | 7 + plugins/explore-react/package.json | 2 +- plugins/explore/CHANGELOG.md | 12 + plugins/explore/package.json | 2 +- plugins/firehydrant/CHANGELOG.md | 10 + plugins/firehydrant/package.json | 2 +- plugins/fossa/CHANGELOG.md | 12 + plugins/fossa/package.json | 2 +- plugins/gcalendar/CHANGELOG.md | 10 + plugins/gcalendar/package.json | 2 +- plugins/gcp-projects/CHANGELOG.md | 9 + plugins/gcp-projects/package.json | 2 +- plugins/git-release-manager/CHANGELOG.md | 10 + plugins/git-release-manager/package.json | 2 +- plugins/github-actions/CHANGELOG.md | 12 + plugins/github-actions/package.json | 2 +- plugins/github-deployments/CHANGELOG.md | 14 + plugins/github-deployments/package.json | 2 +- plugins/github-issues/CHANGELOG.md | 14 + plugins/github-issues/package.json | 2 +- .../github-pull-requests-board/CHANGELOG.md | 12 + .../github-pull-requests-board/package.json | 2 +- plugins/gitops-profiles/CHANGELOG.md | 9 + plugins/gitops-profiles/package.json | 2 +- plugins/gocd/CHANGELOG.md | 12 + plugins/gocd/package.json | 2 +- plugins/graphiql/CHANGELOG.md | 9 + plugins/graphiql/package.json | 2 +- plugins/graphql-backend/CHANGELOG.md | 10 + plugins/graphql-backend/package.json | 2 +- plugins/home/CHANGELOG.md | 13 + plugins/home/package.json | 2 +- plugins/ilert/CHANGELOG.md | 12 + plugins/ilert/package.json | 2 +- plugins/jenkins-backend/CHANGELOG.md | 14 + plugins/jenkins-backend/package.json | 2 +- plugins/jenkins-common/CHANGELOG.md | 8 + plugins/jenkins-common/package.json | 2 +- plugins/jenkins/CHANGELOG.md | 13 + plugins/jenkins/package.json | 2 +- plugins/kafka-backend/CHANGELOG.md | 10 + plugins/kafka-backend/package.json | 2 +- plugins/kafka/CHANGELOG.md | 12 + plugins/kafka/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 13 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 7 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 13 + plugins/kubernetes/package.json | 2 +- plugins/lighthouse/CHANGELOG.md | 12 + plugins/lighthouse/package.json | 2 +- plugins/newrelic-dashboard/CHANGELOG.md | 11 + plugins/newrelic-dashboard/package.json | 2 +- plugins/newrelic/CHANGELOG.md | 9 + plugins/newrelic/package.json | 2 +- plugins/org/CHANGELOG.md | 11 + plugins/org/package.json | 2 +- plugins/pagerduty/CHANGELOG.md | 12 + plugins/pagerduty/package.json | 2 +- plugins/periskop-backend/CHANGELOG.md | 9 + plugins/periskop-backend/package.json | 2 +- plugins/periskop/CHANGELOG.md | 12 + plugins/periskop/package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 13 + plugins/permission-backend/package.json | 2 +- plugins/permission-common/CHANGELOG.md | 37 + plugins/permission-common/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 39 + plugins/permission-node/package.json | 2 +- plugins/permission-react/CHANGELOG.md | 9 + plugins/permission-react/package.json | 2 +- plugins/playlist-backend/CHANGELOG.md | 34 + plugins/playlist-backend/package.json | 2 +- plugins/playlist-common/CHANGELOG.md | 7 + plugins/playlist-common/package.json | 2 +- plugins/playlist/CHANGELOG.md | 17 + plugins/playlist/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- plugins/rollbar-backend/CHANGELOG.md | 9 + plugins/rollbar-backend/package.json | 2 +- plugins/rollbar/CHANGELOG.md | 11 + plugins/rollbar/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 26 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 8 + plugins/scaffolder-common/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 23 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 10 + plugins/search-backend-module-pg/package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 12 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 16 + plugins/search-backend/package.json | 2 +- plugins/search-common/CHANGELOG.md | 8 + plugins/search-common/package.json | 2 +- plugins/search-react/CHANGELOG.md | 12 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 17 + plugins/search/package.json | 2 +- plugins/sentry/CHANGELOG.md | 11 + plugins/sentry/package.json | 2 +- plugins/shortcuts/CHANGELOG.md | 10 + plugins/shortcuts/package.json | 2 +- plugins/sonarqube-backend/CHANGELOG.md | 9 + plugins/sonarqube-backend/package.json | 2 +- plugins/sonarqube/CHANGELOG.md | 11 + plugins/sonarqube/package.json | 2 +- plugins/splunk-on-call/CHANGELOG.md | 11 + plugins/splunk-on-call/package.json | 2 +- plugins/stack-overflow-backend/CHANGELOG.md | 9 + plugins/stack-overflow-backend/package.json | 2 +- plugins/stack-overflow/CHANGELOG.md | 12 + plugins/stack-overflow/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/tech-insights-backend/CHANGELOG.md | 15 + plugins/tech-insights-backend/package.json | 2 +- plugins/tech-insights-common/CHANGELOG.md | 7 + plugins/tech-insights-common/package.json | 2 +- plugins/tech-insights-node/CHANGELOG.md | 11 + plugins/tech-insights-node/package.json | 2 +- plugins/tech-insights/CHANGELOG.md | 14 + plugins/tech-insights/package.json | 2 +- plugins/tech-radar/CHANGELOG.md | 9 + plugins/tech-radar/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 16 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 20 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 12 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 11 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 18 + plugins/techdocs/package.json | 2 +- plugins/todo-backend/CHANGELOG.md | 12 + plugins/todo-backend/package.json | 2 +- plugins/todo/CHANGELOG.md | 12 + plugins/todo/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 13 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 12 + plugins/user-settings/package.json | 2 +- plugins/vault-backend/CHANGELOG.md | 11 + plugins/vault-backend/package.json | 2 +- plugins/vault/CHANGELOG.md | 12 + plugins/vault/package.json | 2 +- plugins/xcmetrics/CHANGELOG.md | 10 + plugins/xcmetrics/package.json | 2 +- 329 files changed, 4446 insertions(+), 165 deletions(-) create mode 100644 docs/releases/v1.7.0-next.2-changelog.md diff --git a/.changeset/pre.json b/.changeset/pre.json index c5f8b30596..33b9221a6a 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -177,47 +177,68 @@ }, "changesets": [ "blue-lions-greet", + "brave-goats-rush", "brave-peaches-brush", "breezy-pots-worry", "bright-rules-shout", + "brown-grapes-battle", "calm-moose-fetch", "calm-pianos-burn", "clean-camels-sneeze", "clever-ties-burn", "cuddly-bikes-tease", + "cuddly-ways-fail", "curly-rats-itch", "curvy-kiwis-fold", "curvy-lemons-change", + "curvy-pets-wash", "dry-shirts-attack", "dull-rocks-warn", + "early-rocks-smell", "eleven-apples-accept", "fair-tools-melt", "fifty-berries-learn", + "five-tables-grow", + "fresh-donuts-arrive", + "fuzzy-dolls-give", + "fuzzy-dolls-shake", + "giant-pianos-repeat", + "great-numbers-reply", "grumpy-pans-knock", "hot-geese-vanish", "itchy-schools-run", + "kind-bees-suffer", "kind-penguins-report", "large-dingos-juggle", + "large-jars-fry", "lazy-beds-pull", + "lazy-eyes-switch", "lazy-fireants-check", + "little-hats-yell", "little-roses-rule", "loud-dots-sit", "lovely-peaches-fold", + "lucky-beans-bathe", "lucky-cows-boil", "mean-spiders-design", "mighty-lions-march", + "moody-carrots-shout", "odd-pandas-suffer", "odd-singers-taste", "old-cobras-suffer", + "old-melons-bathe", "old-needles-brake", "perfect-moose-drum", "plenty-kids-fetch", "plenty-laws-end", "poor-clouds-ring", "pretty-buttons-develop", + "purple-masks-travel", + "quick-meals-talk", "quiet-dancers-jog", "quiet-hats-kick", "quiet-ligers-draw", + "rare-cougars-cross", "rare-hotels-cough", "red-dots-sleep", "red-pants-rush", @@ -225,6 +246,7 @@ "renovate-a02d90b", "rich-carrots-reflect", "rude-bulldogs-sleep", + "rude-tomatoes-behave", "search-bobcats-love", "search-cars-hide", "search-clouds-begin", @@ -232,10 +254,13 @@ "search-dull-planes-prove", "search-rats-grin", "selfish-turkeys-exist", + "seven-panthers-chew", "shaggy-books-smell", + "short-plants-return", "silent-bees-repeat", "silly-rules-join", "sixty-items-nail", + "slimy-suns-learn", "slow-mirrors-eat", "smooth-tables-pull", "soft-falcons-love", @@ -243,16 +268,20 @@ "sweet-insects-camp", "swift-phones-cheat", "tall-baboons-deliver", + "techdocs-olive-worms-yell", "tender-drinks-drive", "tender-parrots-peel", "thick-kings-destroy", "tiny-mails-bathe", "tough-hairs-sparkle", + "tough-seahorses-learn", "twelve-melons-notice", + "unlucky-seas-sip", "warm-days-watch", "wild-weeks-live", "wise-ligers-scream", "yellow-lemons-march", - "young-bugs-remember" + "young-bugs-remember", + "young-olives-cheer" ] } diff --git a/docs/releases/v1.7.0-next.2-changelog.md b/docs/releases/v1.7.0-next.2-changelog.md new file mode 100644 index 0000000000..d5a9b0ddad --- /dev/null +++ b/docs/releases/v1.7.0-next.2-changelog.md @@ -0,0 +1,2127 @@ +# Release v1.7.0-next.2 + +## @backstage/plugin-bazaar-backend@0.2.0-next.2 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-catalog-backend@1.5.0-next.2 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-catalog-import@0.9.0-next.2 + +### Minor Changes + +- b2e6cb6acf: **Breaking** + Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + + Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + + ```ts + // catalog.ts + import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); + ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + +## @backstage/plugin-catalog-node@1.2.0-next.2 + +### Minor Changes + +- 404366c853: Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-common@0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-node@0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-playlist-backend@0.2.0-next.2 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-scaffolder-backend@1.7.0-next.2 + +### Minor Changes + +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-techdocs-backend@1.4.0-next.2 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/app-defaults@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/backend-app-api@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/backend-common@0.15.2-next.2 + +### Patch Changes + +- c44cf412de: Fix BitBucket server integration +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + +## @backstage/backend-plugin-api@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/backend-tasks@0.3.6-next.2 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/catalog-client@1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/catalog-model@1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/cli@0.20.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32-next.2 + +### Patch Changes + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/integration-react@1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + +## @backstage/test-utils@1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-adr@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-adr-backend@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-adr-common@0.2.2-next.2 + +## @backstage/plugin-adr-common@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-airbrake@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/dev-utils@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-allure@0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-api-docs@0.8.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-backend@0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-azure-devops@0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-bazaar@0.1.25-next.2 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-bitrise@0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog@1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4-next.2 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4-next.2 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.1.8-next.2 + +### Patch Changes + +- 7022aebf35: Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-common@1.0.7-next.2 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + +## @backstage/plugin-catalog-graph@0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-react@1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-cicd-statistics@0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/plugin-cicd-statistics@0.1.12-next.2 + +## @backstage/plugin-circleci@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-codescene@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@1.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.2 + +## @backstage/plugin-explore-react@0.0.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-firehydrant@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2-next.2 + +### Patch Changes + +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-catalog-graphql@0.3.14-next.2 + +## @backstage/plugin-home@0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + +## @backstage/plugin-ilert@0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins-backend@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-jenkins-common@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + +## @backstage/plugin-kafka@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-kubernetes@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## @backstage/plugin-kubernetes-backend@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## @backstage/plugin-kubernetes-common@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + +## @backstage/plugin-lighthouse@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-org@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-permission-backend@0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-permission-react@0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-playlist@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-playlist-common@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + +## @backstage/plugin-proxy-backend@0.2.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-rollbar@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-scaffolder@1.7.0-next.2 + +### Patch Changes + +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-search-backend@1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-search-backend-node@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-search-common@1.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-react@1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-sentry@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-splunk-on-call@0.3.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-home@0.4.26-next.2 + +## @backstage/plugin-stack-overflow-backend@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-tech-insights@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-backend@0.5.3-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-common@0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-radar@0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-node@1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-techdocs-react@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-user-settings@0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-user-settings-backend@0.1.1-next.2 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-vault@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-xcmetrics@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## example-app@0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + +## example-backend@0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + +## example-backend-next@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/backend-defaults@0.1.2-next.2 + +## techdocs-cli-embedded-app@0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @internal/plugin-catalog-customized@0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + +## @internal/plugin-todo-list@1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @internal/plugin-todo-list-common@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 diff --git a/package.json b/package.json index 1013b2a049..4270ef9824 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@types/react": "^17", "@types/react-dom": "^17" }, - "version": "1.7.0-next.1", + "version": "1.7.0-next.2", "dependencies": { "@backstage/errors": "workspace:^", "@manypkg/get-packages": "^1.1.3", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index c4082ad530..1b07baeade 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 1.0.7-next.1 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 476509730a..eb62cab691 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "1.0.7-next.1", + "version": "1.0.7-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index d492b19a1a..e10bfddff8 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,67 @@ # example-app +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + ## 0.2.76-next.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index fa8bb5c5ee..561b8eae30 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.76-next.1", + "version": "0.2.76-next.2", "private": true, "backstage": { "role": "frontend" diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 3823d025ac..2b179d7d7a 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-app-api +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index c454785aa2..fd27521572 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-app-api", "description": "Core API used by Backstage backend apps", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 143b6d28c9..3029c424fc 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-common +## 0.15.2-next.2 + +### Patch Changes + +- c44cf412de: Fix BitBucket server integration +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.15.2-next.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b6b0c5e35a..133c8590fc 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.15.2-next.1", + "version": "0.15.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 8d980fe1bc..986a4a39f2 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-defaults +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + ## 0.1.2-next.1 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 1d0a3f09f6..c0344572fa 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.1.2-next.1", + "version": "0.1.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index e8d6cefa1b..5849d5b088 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,15 @@ # example-backend-next +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/backend-defaults@0.1.2-next.2 + ## 0.0.4-next.1 ### Patch Changes diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 7db8ef9775..8965c8b1c8 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.4-next.1", + "version": "0.0.4-next.2", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 21897d0a97..230092c0ba 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-plugin-api +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.3-next.1 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 80e3a8adfa..389e36aa93 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "description": "Core API used by Backstage backend plugins", - "version": "0.1.3-next.1", + "version": "0.1.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 97468f3856..bff6fa4e7d 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-tasks +## 0.3.6-next.2 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.3.6-next.1 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 9fc7d6cbae..06cbe14bf7 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.3.6-next.1", + "version": "0.3.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 753ad24289..ab7f6ef60a 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-test-utils +## 0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.29-next.1 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 5249196860..d9ab0e195d 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.29-next.1", + "version": "0.1.29-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index c5dc2f71bd..c4df56a55f 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,47 @@ # example-backend +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.2.76-next.1 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 75a116ee2a..eb13572859 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.76-next.1", + "version": "0.2.76-next.2", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 0e5a847703..27c2f9d1bf 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.1.1-next.1 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 31b8654556..d27efc6257 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "1.1.1-next.1", + "version": "1.1.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 6352f13760..ac53d99efc 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/catalog-model +## 1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.2-next.1 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index b1a83fbeb0..0cfebe36d6 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-model", "description": "Types and validators that help describe the model of a Backstage Catalog", - "version": "1.1.2-next.1", + "version": "1.1.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 90caca8f18..9263da9053 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/cli +## 0.20.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + ## 0.20.0-next.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 66f69772d5..213c6dad00 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.20.0-next.1", + "version": "0.20.0-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 673054c186..e95f39dcfe 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/codemods +## 0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.1.40-next.1 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 4996887eed..37342231a8 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.40-next.1", + "version": "0.1.40-next.2", "publishConfig": { "access": "public", "main": "dist/index.cjs.js" diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 0c1afa75c1..42eaa97e53 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/config-loader +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.5-next.1 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 3fb1da9e1f..7bd0ffb1bf 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "1.1.5-next.1", + "version": "1.1.5-next.2", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 151201bfa7..a08d4341e4 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/config +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 1.0.3-next.1 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 2305784051..55891f8c63 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "1.0.3-next.1", + "version": "1.0.3-next.2", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 8914390d89..0c197cfb1e 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-app-api +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.1.1-next.1 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 219244eacc..c1beb853cb 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.1.1-next.1", + "version": "1.1.1-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index a27be20b9f..cb75cb5fb8 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-components +## 0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + ## 0.11.2-next.1 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index a32d8c9af8..3a58be3529 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.11.2-next.1", + "version": "0.11.2-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 26e18ea244..f0ac2c4dfb 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/core-plugin-api +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.0.7-next.1 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 48b78a1daf..c1097bdf9e 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "1.0.7-next.1", + "version": "1.0.7-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 0e1d1a98af..2640775b77 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/create-app +## 0.4.32-next.2 + +### Patch Changes + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.4.32-next.1 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 9633c890fa..a42c7830df 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.32-next.1", + "version": "0.4.32-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 01c97a4c74..31ca46b945 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/dev-utils +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + ## 1.0.7-next.1 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 60c877f736..990b45d692 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "1.0.7-next.1", + "version": "1.0.7-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 26f45611fc..fe478f0bd7 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/errors +## 1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 1.1.2-next.1 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index ad10bb7ed5..e78ecb45d2 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/errors", "description": "Common utilities for error handling within Backstage", - "version": "1.1.2-next.1", + "version": "1.1.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 00439a9771..a547cf131a 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/integration-react +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + ## 1.1.5-next.1 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index cab328f88d..f097a116b4 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "1.1.5-next.1", + "version": "1.1.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 134dc34c11..e3433efaf8 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/integration +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.3.2-next.1 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 169bf56353..cf2c7af7fe 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "1.3.2-next.1", + "version": "1.3.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 49b59771c3..cc2fdb84a3 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 0.2.75-next.1 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 25eb9f2b27..b6b2a40832 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.75-next.1", + "version": "0.2.75-next.2", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index c04b3b8681..3fee2b5e70 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @techdocs/cli +## 1.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + ## 1.2.2-next.1 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 3f00391865..63dbe14e03 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.2.2-next.1", + "version": "1.2.2-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 28a4a92294..4495c72de1 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/test-utils +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 1.2.1-next.1 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a7bcbab826..f950c42dde 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "1.2.1-next.1", + "version": "1.2.1-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md index a13f16e340..3349492a65 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-adr-backend +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-adr-common@0.2.2-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index ff232e6b69..903e462bc9 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr-backend", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index d1738d9920..3472fc1e18 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-adr-common +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index 8358349631..7bfd4d4aa2 100644 --- a/plugins/adr-common/package.json +++ b/plugins/adr-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-adr-common", "description": "Common functionalities for the adr plugin", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md index 7ac3655e03..544b13ef77 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-adr +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 4eb9cf7b03..dc324e05f2 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index b61f76db54..45a049c672 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-airbrake-backend +## 0.2.10-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 2817861740..a96a05e753 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake-backend", - "version": "0.2.10-next.1", + "version": "0.2.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index c466c6cb3a..cf7f67e46b 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-airbrake +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/dev-utils@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 28677b7948..d81ddf3cdf 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index ae170e29c7..c9f80be792 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-allure +## 0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.26-next.1 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 1691a77ecf..395295512a 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.26-next.1", + "version": "0.1.26-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index 13c445f246..8e7cbbed8d 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-analytics-module-ga +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.21-next.1 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 87d6d4f0fc..56489d3753 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.21-next.1", + "version": "0.1.21-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 5cd28f0827..37504b440d 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-apache-airflow +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 5f9c1b4f9d..f3baa62412 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.2.3-next.1", + "version": "0.2.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 432ff12f76..75b2e41b0b 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-api-docs +## 0.8.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.8.10-next.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bd38fa9c65..695cbbc1ef 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.8.10-next.1", + "version": "0.8.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md index a1c6c6c8df..405f7558ad 100644 --- a/plugins/apollo-explorer/CHANGELOG.md +++ b/plugins/apollo-explorer/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-apollo-explorer +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.3-next.1 ### Patch Changes diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index a5e598ede1..ddf5996037 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apollo-explorer", - "version": "0.1.3-next.1", + "version": "0.1.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index b1b112a030..8e9ad5420b 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-app-backend +## 0.3.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/types@1.0.0 + ## 0.3.37-next.1 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index db100ea12b..64378b4adf 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.37-next.1", + "version": "0.3.37-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index afa2d700fd..54cce8a7b0 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-auth-backend +## 0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.17.0-next.1 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index be02a90a71..0a199443b1 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.17.0-next.1", + "version": "0.17.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 147861383f..b84e726467 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-node +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.6-next.1 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 7c0c3b51e2..7649826982 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.2.6-next.1", + "version": "0.2.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 00cc426c0e..3516169016 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-azure-devops-backend +## 0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.3.16-next.1 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 27be6291ed..062fa44c0b 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.3.16-next.1", + "version": "0.3.16-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 4919b1226d..f3538acd45 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-azure-devops +## 0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.2.1-next.1 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 14f2c29310..dca00111f6 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.2.1-next.1", + "version": "0.2.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index 770491ec15..0b980d94c4 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-badges-backend +## 0.1.31-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.31-next.1 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 678dc8d56e..ea3524ce8a 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.31-next.1", + "version": "0.1.31-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 5adc7d112b..3692f7280a 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-badges +## 0.2.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.34-next.1 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 0f05f6fa25..18d9f76ce6 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.34-next.1", + "version": "0.2.34-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index bb39eff121..53d888b247 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-bazaar-backend +## 0.2.0-next.2 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.21-next.1 ### Patch Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 31ae4c6cb5..9a203ed0f2 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.1.21-next.1", + "version": "0.2.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index b2d0e8a8d4..c5042a6585 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-bazaar +## 0.1.25-next.2 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.25-next.1 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index e019784cc5..94905d0387 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.25-next.1", + "version": "0.1.25-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 2dc4bd1ae3..0fb56e97ae 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.2 + ## 0.2.0-next.1 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 10d443e2a5..50b2706494 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", "description": "Common functionalities for bitbucket-cloud plugins", - "version": "0.2.0-next.1", + "version": "0.2.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index d8781740ec..987576897a 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-bitrise +## 0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.37-next.1 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 0645020104..29c6be025c 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.37-next.1", + "version": "0.1.37-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 54717bb37d..e1b79a9bee 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 8be3ca3031..8a511b0019 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 4342307cf5..bdd495a472 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 0385655259..5ccc68a70e 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index bbc999591c..4df7a0fd82 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.1.4-next.2 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketCloud/discovery + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 54603b128f..df8fd11e8c 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.1.4-next.1", + "version": "0.1.4-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index ca248ce215..7ba7e97d21 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.1.2-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 2490b79ffa..94b9cdd780 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.2-next.1", + "version": "0.1.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index eb9eb97acc..448b4b6eee 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.2.4-next.2 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 61657cfafd..dd6ebbf75c 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.2.4-next.1", + "version": "0.2.4-next.2", "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 8e1b11c1f7..bba01147a2 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.1.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index e3665a96ae..23ae165a01 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.5-next.1", + "version": "0.1.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 1a7a4a512c..e52ec4390b 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-github +## 0.1.8-next.2 + +### Patch Changes + +- 7022aebf35: Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 1fb82a648a..f6b040c177 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github", "description": "A Backstage catalog backend module that helps integrate towards GitHub", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index add7b604d3..aa3d389714 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 510a4eb3be..60c16737c7 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 305eed8686..b1a789fb17 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.5.4-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index eaa3929ade..b0587a0d7d 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend module that helps integrate towards LDAP", - "version": "0.5.4-next.1", + "version": "0.5.4-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 2da4607869..c90df48b93 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index d3ac35f743..9532cbff7a 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", - "version": "0.4.3-next.1", + "version": "0.4.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index d098727b3e..981f652bd0 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.3-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index dded553b09..8465e232d0 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.3-next.1", + "version": "0.1.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index d3b24efb6f..4182d0acb3 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-catalog-backend +## 1.5.0-next.2 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + ## 1.4.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 4d127a4d43..8ace08abcf 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "1.4.1-next.1", + "version": "1.5.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 04fb82c760..f1a54baa1f 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-common +## 1.0.7-next.2 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + ## 1.0.7-next.1 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 12dcbc4ccf..981adff43a 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "1.0.7-next.1", + "version": "1.0.7-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-customized/CHANGELOG.md b/plugins/catalog-customized/CHANGELOG.md index 8ba2f88dce..b7542abb38 100644 --- a/plugins/catalog-customized/CHANGELOG.md +++ b/plugins/catalog-customized/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-catalog-customized +## 0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + ## 0.0.3-next.1 ### Patch Changes diff --git a/plugins/catalog-customized/package.json b/plugins/catalog-customized/package.json index ddd80bee43..82452a97fc 100644 --- a/plugins/catalog-customized/package.json +++ b/plugins/catalog-customized/package.json @@ -1,7 +1,7 @@ { "name": "@internal/plugin-catalog-customized", "description": "The internal Backstage Customizable plugin for browsing the Backstage catalog", - "version": "0.0.3-next.1", + "version": "0.0.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 724a4bf1a2..6eb03b86b8 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-graph +## 0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.2.22-next.1 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 8fd71845c2..cc4a598380 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.22-next.1", + "version": "0.2.22-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index f185f18bc6..66cf817d89 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-graphql +## 0.3.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + ## 0.3.14-next.1 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 96ec786f27..e404f62ea5 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-graphql", "description": "An experimental Backstage catalog GraphQL module", - "version": "0.3.14-next.1", + "version": "0.3.14-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 5a24cbabcc..6f22929828 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-catalog-import +## 0.9.0-next.2 + +### Minor Changes + +- b2e6cb6acf: **Breaking** + Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + + Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + + ```ts + // catalog.ts + import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); + ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + ## 0.8.13-next.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index abb114ad2f..be642db914 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.8.13-next.1", + "version": "0.9.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 59a28582b6..48f627a9d7 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-node +## 1.2.0-next.2 + +### Minor Changes + +- 404366c853: Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.1-next.1 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index f008e38e6b..f249b10588 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-node", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", - "version": "1.1.1-next.1", + "version": "1.2.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index bfac97c526..1f98467925 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-react +## 1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.2.0-next.1 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index c9695b71bb..4d64ae3953 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.2.0-next.1", + "version": "1.2.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 53ab64b093..6974afe720 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog +## 1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 1.6.0-next.1 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 5f2b659e13..ffa6b92d86 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "1.6.0-next.1", + "version": "1.6.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md index 63da967bb5..5e1a1139fe 100644 --- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md +++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cicd-statistics-module-gitlab +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/plugin-cicd-statistics@0.1.12-next.2 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index 9cf5054269..efbc8a6f3b 100644 --- a/plugins/cicd-statistics-module-gitlab/package.json +++ b/plugins/cicd-statistics-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics-module-gitlab", "description": "CI/CD Statistics plugin module; Gitlab CICD", - "version": "0.1.6-next.1", + "version": "0.1.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 7031927d9e..d12c15c85b 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cicd-statistics +## 0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + ## 0.1.12-next.1 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index 8ca3be9ecf..faf51957db 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.12-next.1", + "version": "0.1.12-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index d8ca85d777..79e553dd86 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-circleci +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 8b0e80fd6b..39759afb0a 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 74abd2ca13..53f602fc70 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-cloudbuild +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index c044ec187b..5e7f671787 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index 04aa2d0ffd..5f66a7e668 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-code-climate +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 2896b96a32..c26aa027c6 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-climate", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index f4f08bdc1a..ab69e4015c 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-code-coverage-backend +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 3f3eaec53b..5593dc3b35 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.2.3-next.1", + "version": "0.2.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index dda04bbd71..3b01d0c39d 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-code-coverage +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index ed09b9fd6a..1bfd9ca444 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.2.3-next.1", + "version": "0.2.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md index c8d90ebb04..fd2d97ac41 100644 --- a/plugins/codescene/CHANGELOG.md +++ b/plugins/codescene/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-codescene +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.5-next.1 ### Patch Changes diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 115b342d19..544d865aa8 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-codescene", - "version": "0.1.5-next.1", + "version": "0.1.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 1a54cae914..c2d00a5c6b 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-config-schema +## 0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.1.33-next.1 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 790905019e..2bb0233302 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.33-next.1", + "version": "0.1.33-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 8f672c101d..4fe1096859 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-cost-insights +## 0.11.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + ## 0.11.32-next.1 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 13fb2c2982..f891016c10 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.32-next.1", + "version": "0.11.32-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/dynatrace/CHANGELOG.md b/plugins/dynatrace/CHANGELOG.md index b6844a870d..6f6e756b95 100644 --- a/plugins/dynatrace/CHANGELOG.md +++ b/plugins/dynatrace/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-dynatrace +## 1.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 1.0.0-next.1 ### Patch Changes diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json index d18264a235..6eb57391bc 100644 --- a/plugins/dynatrace/package.json +++ b/plugins/dynatrace/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-dynatrace", - "version": "1.0.0-next.1", + "version": "1.0.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 6a3e043504..4e4fc61b26 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @internal/plugin-todo-list-backend +## 1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.0.6-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 839eb98581..67b029819e 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.6-next.1", + "version": "1.0.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 9ea7667216..aacd75d4d8 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 522b80a7a1..fac4edf22d 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.5-next.1", + "version": "1.0.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 63f03a4ea2..7bd1f2f3f2 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/plugin-todo-list +## 1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 1.0.6-next.1 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index ac96785998..4dfe9ee2a7 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.6-next.1", + "version": "1.0.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index ed20a86e6f..b3657b2ab6 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-explore-react +## 0.0.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.2 + ## 0.0.22-next.1 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 309766b66b..16533b3108 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.22-next.1", + "version": "0.0.22-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index e91e1b52e5..87b43cb012 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-explore +## 0.3.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.2 + ## 0.3.41-next.1 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 1d84bb65c8..c03993ee24 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.41-next.1", + "version": "0.3.41-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 21bd34e61f..3c9630f544 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-firehydrant +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.27-next.1 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index e1e1498823..e556e7b29c 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.27-next.1", + "version": "0.1.27-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index a70ff88ab1..876a62aa4a 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-fossa +## 0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.42-next.1 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 943557e461..249a0c4f0e 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.42-next.1", + "version": "0.2.42-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md index 4237afcf25..a1fefa672d 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-gcalendar +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.3.6-next.1 ### Patch Changes diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 6aa76071c2..96c1afb493 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.3.6-next.1", + "version": "0.3.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 30ded2e01f..242bc197bd 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-gcp-projects +## 0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.29-next.1 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index e8c5d19b77..def4421995 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.29-next.1", + "version": "0.3.29-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index cc7ee19c95..b49e46bf18 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-git-release-manager +## 0.3.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + ## 0.3.23-next.1 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 09533a3788..924a0e1939 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.23-next.1", + "version": "0.3.23-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index d9023a2d12..ff9a3e8f23 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-github-actions +## 0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + ## 0.5.10-next.1 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index bc14ca6192..5a6b0ffdb3 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.5.10-next.1", + "version": "0.5.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 721a9d3602..2459be1a5f 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-github-deployments +## 0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + ## 0.1.41-next.1 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 705b76454c..85d8747034 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.41-next.1", + "version": "0.1.41-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-issues/CHANGELOG.md b/plugins/github-issues/CHANGELOG.md index 4fc787427c..5f32e404c1 100644 --- a/plugins/github-issues/CHANGELOG.md +++ b/plugins/github-issues/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-github-issues +## 0.1.2-next.2 + +### Patch Changes + +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.2-next.1 ### Patch Changes diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json index 5df0e2bb55..bcc06311a6 100644 --- a/plugins/github-issues/package.json +++ b/plugins/github-issues/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-issues", - "version": "0.1.2-next.1", + "version": "0.1.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md index b128055cef..c112cba1c4 100644 --- a/plugins/github-pull-requests-board/CHANGELOG.md +++ b/plugins/github-pull-requests-board/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-github-pull-requests-board +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index a9b17ded8a..cc15d368ee 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-pull-requests-board", "description": "A Backstage plugin that allows you to see all open Pull Requests for all the repositories owned by your team", - "version": "0.1.4-next.1", + "version": "0.1.4-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index 8a0c667f62..38d9ba91ec 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-gitops-profiles +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.28-next.1 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 738ac33754..399f8151fd 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.28-next.1", + "version": "0.3.28-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index 5e78aa4af5..491d9f6fd7 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gocd +## 0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.16-next.1 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 59d60d39ef..5fda8c6b63 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.16-next.1", + "version": "0.1.16-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index 0b3b43c598..0466df3ac0 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-graphiql +## 0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.2.42-next.1 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 5b90ad15cf..69eb82105d 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.42-next.1", + "version": "0.2.42-next.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index cbe3284702..88e1a911d7 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-graphql-backend +## 0.1.27-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-catalog-graphql@0.3.14-next.2 + ## 0.1.27-next.1 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index b8bb08cecb..b71b083348 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-backend", "description": "An experimental Backstage backend plugin for GraphQL", - "version": "0.1.27-next.1", + "version": "0.1.27-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 6996036d16..d1b67ec974 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-home +## 0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + ## 0.4.26-next.1 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index eac72f48d2..e8538efddf 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.26-next.1", + "version": "0.4.26-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 96bf455e49..81ad9057b5 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-ilert +## 0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.36-next.1 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 11bba41fcd..78391b2d1e 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.36-next.1", + "version": "0.1.36-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index c4b15f8cdf..aad6ab6887 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-jenkins-backend +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.27-next.1 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index bc9bfd49c2..4d2118c270 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.27-next.1", + "version": "0.1.27-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index 7329ecd7e4..01a30d35e0 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-jenkins-common +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + ## 0.1.9-next.1 ### Patch Changes diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 9696000d03..2bdb7cfb7a 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-common", - "version": "0.1.9-next.1", + "version": "0.1.9-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index d113d79897..8809f31022 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-jenkins +## 0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.7.9-next.1 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index cf6ad8c2d0..37ee196701 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.7.9-next.1", + "version": "0.7.9-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index 1f88887c18..06cfade726 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kafka-backend +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.30-next.1 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index d9ed476b9a..aea9fb5e30 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.2.30-next.1", + "version": "0.2.30-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 1be95d1619..b1aa676dc2 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kafka +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index bc142ced57..c617fad4e1 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 08008f5fe1..6c70dfc7cc 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes-backend +## 0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + ## 0.7.3-next.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index e9a790b2ac..25c112e9b6 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.7.3-next.1", + "version": "0.7.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 7071045b04..bb5947e966 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-kubernetes-common +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 97c42e823d..a79b396256 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.4.3-next.1", + "version": "0.4.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 75d2c3dc46..b1c97431f9 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes +## 0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + ## 0.7.3-next.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 09b177eea2..ef8cfd1300 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.7.3-next.1", + "version": "0.7.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 3ca5529726..2fd7b2391c 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-lighthouse +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f05e4f9732..aef3967bff 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index 82dd592c92..5de697c86b 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-newrelic-dashboard +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index a7a791db57..0408a79611 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.2.3-next.1", + "version": "0.2.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 0f2c3808b1..41b7327cef 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-newrelic +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.28-next.1 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 270a8a3961..81161bf2aa 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.28-next.1", + "version": "0.3.28-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index c66dbe7484..195cf5c171 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org +## 0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.5.10-next.1 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index c9e72c9584..d83ecf73d6 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.5.10-next.1", + "version": "0.5.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 0638130d2e..f2f4291d7f 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-pagerduty +## 0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.5.3-next.1 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 3edffd0a52..ffef2d1479 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.5.3-next.1", + "version": "0.5.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index 403f762c4e..a226725ba8 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-periskop-backend +## 0.1.8-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 12c4f62ca6..87b95068ff 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop-backend", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index a263bd1ff4..e36887842a 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-periskop +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 944f614b17..b21bb2e4b7 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 7e2f0dff31..1d6aaedf20 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-permission-backend +## 0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.5.12-next.1 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index aabcbafde2..0706673183 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.12-next.1", + "version": "0.5.12-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index b013d4bd09..b0b2901f69 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-permission-common +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.6.5-next.1 ### Patch Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index f9f9ce6f94..3b56c927fe 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.6.5-next.1", + "version": "0.7.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 9ca95faa76..851a706df4 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-permission-node +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.6.6-next.1 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 588c0aafe4..d7c05004d7 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.6.6-next.1", + "version": "0.7.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 6e4996b989..537513b221 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-react +## 0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + ## 0.4.6-next.1 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 27660b6a7a..7fad53b870 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.6-next.1", + "version": "0.4.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-backend/CHANGELOG.md b/plugins/playlist-backend/CHANGELOG.md index 1799243663..8997cd6cb8 100644 --- a/plugins/playlist-backend/CHANGELOG.md +++ b/plugins/playlist-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-playlist-backend +## 0.2.0-next.2 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index 136d2a2108..3d817ad9ec 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist-backend", - "version": "0.1.1-next.1", + "version": "0.2.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-common/CHANGELOG.md b/plugins/playlist-common/CHANGELOG.md index aeba2f2cb1..9867acbc4e 100644 --- a/plugins/playlist-common/CHANGELOG.md +++ b/plugins/playlist-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-playlist-common +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index ddb822c47a..9a4cfd7cd7 100644 --- a/plugins/playlist-common/package.json +++ b/plugins/playlist-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-playlist-common", "description": "Common functionalities for the playlist plugin", - "version": "0.1.1-next.1", + "version": "0.1.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist/CHANGELOG.md b/plugins/playlist/CHANGELOG.md index 705a8b24e1..24c69e69a1 100644 --- a/plugins/playlist/CHANGELOG.md +++ b/plugins/playlist/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-playlist +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index 883c83962b..442efe5a94 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist", - "version": "0.1.1-next.1", + "version": "0.1.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index fb4ab82cf4..adff4261cd 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.2.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.2.31-next.1 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 2ce2a23420..2faa206e16 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.31-next.1", + "version": "0.2.31-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index b412e2e913..103e2ea54f 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-rollbar-backend +## 0.1.34-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.34-next.1 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index e4b9a7011c..b6d12a39bb 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.34-next.1", + "version": "0.1.34-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 615a8d13a3..3c07a80e52 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-rollbar +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.4.10-next.1 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 3c5fd7295a..fd0fec5842 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.4.10-next.1", + "version": "0.4.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index ce3ab79f21..68ba110e32 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.2.12-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 5f4227b603..ffa77405b7 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.2.12-next.1", + "version": "0.2.12-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index bdc0a6c933..c5d75bbd27 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.4.5-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index c0f0c5e318..d80f36ed5b 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.4.5-next.1", + "version": "0.4.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 02a136e6c5..8acc5e0933 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 79ff057a2a..07cfedbdc8 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.2.10-next.1", + "version": "0.2.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 1e6cb05ec6..993dd1c577 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-scaffolder-backend +## 1.7.0-next.2 + +### Minor Changes + +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + ## 1.7.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 2be7b62f75..63a47cbb7b 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "1.7.0-next.1", + "version": "1.7.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index aba3278a54..4b0beb5c37 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-common +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.2.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index ff3c9a9275..400be3dd00 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "1.2.1-next.1", + "version": "1.2.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 25b7d73e60..24057cec32 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder +## 1.7.0-next.2 + +### Patch Changes + +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + ## 1.7.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 4508a4b49d..4d9e2caa53 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "1.7.0-next.1", + "version": "1.7.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 08deccee3d..4a80290cbc 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 1.0.3-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 49411fafeb..da200b6cb1 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "1.0.3-next.1", + "version": "1.0.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 32111a501d..08f5f971cb 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-pg +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.4.1-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 29f80a9218..7e051bb30b 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.4.1-next.1", + "version": "0.4.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 41b0066847..214e4898b3 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-node +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.0.3-next.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index ffbd089813..5847ca4315 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-node", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", - "version": "1.0.3-next.1", + "version": "1.0.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index fcbfc1d8d5..97126502ef 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend +## 1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.0-next.1 ### Minor Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 5e678e5380..199510b8dc 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "1.1.0-next.1", + "version": "1.1.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 344f82a757..7e5a5ff38e 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-common +## 1.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/types@1.0.0 + ## 1.1.0-next.1 ### Minor Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 99842fba59..e1260a0d1e 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-common", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", - "version": "1.1.0-next.1", + "version": "1.1.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index ade862c218..89fbb389f2 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-react +## 1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.2.0-next.1 ### Minor Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index bf7d87ca7a..62e9ecd6de 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.2.0-next.1", + "version": "1.2.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 9f647525de..c7494af180 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-search +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 1.0.3-next.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 7777eb6565..7a9a16eedc 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "1.0.3-next.1", + "version": "1.0.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index bbb7556046..d5163685ad 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sentry +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 3f35506c06..8d0b049059 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.4.3-next.1", + "version": "0.4.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 65f2c7fbbc..c35c28a29c 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-shortcuts +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.3.2-next.1 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 7300018e80..caf1c1d127 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.3.2-next.1", + "version": "0.3.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube-backend/CHANGELOG.md b/plugins/sonarqube-backend/CHANGELOG.md index ad209e3a25..6747ae9862 100644 --- a/plugins/sonarqube-backend/CHANGELOG.md +++ b/plugins/sonarqube-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-sonarqube-backend +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.2-next.1 ### Patch Changes diff --git a/plugins/sonarqube-backend/package.json b/plugins/sonarqube-backend/package.json index e1f5168ef6..72488804a9 100644 --- a/plugins/sonarqube-backend/package.json +++ b/plugins/sonarqube-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube-backend", - "version": "0.1.2-next.1", + "version": "0.1.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index c6a06294e5..15f5e27677 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sonarqube +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.4.2-next.1 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 02f29fd308..7d7191d17e 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.4.2-next.1", + "version": "0.4.2-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index f71d3557f9..1c8a06ba27 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-splunk-on-call +## 0.3.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.34-next.1 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index c633319121..e082f45af3 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.34-next.1", + "version": "0.3.34-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow-backend/CHANGELOG.md b/plugins/stack-overflow-backend/CHANGELOG.md index 71a6531527..807b7ff4f4 100644 --- a/plugins/stack-overflow-backend/CHANGELOG.md +++ b/plugins/stack-overflow-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-stack-overflow-backend +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json index 551f1694e1..03e45ffa20 100644 --- a/plugins/stack-overflow-backend/package.json +++ b/plugins/stack-overflow-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow-backend", - "version": "0.1.6-next.1", + "version": "0.1.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md index d12473302e..52e0025211 100644 --- a/plugins/stack-overflow/CHANGELOG.md +++ b/plugins/stack-overflow/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-stack-overflow +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-home@0.4.26-next.2 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index ff748f4384..d423b847da 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow", - "version": "0.1.6-next.1", + "version": "0.1.6-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 31039fbee6..bde496d73f 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.1.21-next.1 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 5e4761bc11..cd64ea0742 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.21-next.1", + "version": "0.1.21-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 2940da433a..785f53e06f 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-tech-insights-backend +## 0.5.3-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.5.3-next.1 ### Patch Changes diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index f28dbf4329..16567f4c35 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.5.3-next.1", + "version": "0.5.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-common/CHANGELOG.md b/plugins/tech-insights-common/CHANGELOG.md index 790b4a12ef..490f5cb941 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-tech-insights-common +## 0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 0.2.7-next.1 ### Patch Changes diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index a47a5d1c8d..848454a823 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-common", - "version": "0.2.7-next.1", + "version": "0.2.7-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index e8419a8d3f..62916ad560 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-tech-insights-node +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 22802bccc9..45ca5f2739 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.3.5-next.1", + "version": "0.3.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index f605295d31..99f7551e53 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-tech-insights +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.3.1-next.1 ### Patch Changes diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 8d9b225b29..f4ccbc41e0 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.3.1-next.1", + "version": "0.3.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 24ff4c2f4f..53b30f3a9e 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-tech-radar +## 0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.5.17-next.1 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5dafe2de9b..43c02f838b 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.17-next.1", + "version": "0.5.17-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 5f9f033435..406f837998 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index bdd971912f..3e976d1e06 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.5-next.1", + "version": "1.0.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d2000fba1d..ccd4c4b67f 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-techdocs-backend +## 1.4.0-next.2 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 1.3.1-next.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 2b85508c30..ca1c5b7578 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "1.3.1-next.1", + "version": "1.4.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index d220bc3815..548f30d6f1 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 1e1fd0eede..5fde16fa33 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", "description": "Plugin module for contributed TechDocs Addons", - "version": "1.0.5-next.1", + "version": "1.0.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 1503a665d6..d1f1ea55ce 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-node +## 1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 1.4.1-next.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index ab7267e10b..6b4e822f2f 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-node", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "1.4.1-next.1", + "version": "1.4.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 869edb0a48..fa791fb4ca 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-react +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/version-bridge@1.0.1 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 38a2489782..d9d8b76382 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-react", "description": "Shared frontend utilities for TechDocs and Addons", - "version": "1.0.5-next.1", + "version": "1.0.5-next.2", "publishConfig": { "access": "public", "alphaTypes": "dist/index.alpha.d.ts", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 0d8fdd369d..1c331fd7ea 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs +## 1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 1.3.3-next.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index c915ac515f..f7dbbc06ff 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "1.3.3-next.1", + "version": "1.3.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index 753ab2da44..3ce6301333 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-todo-backend +## 0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.1.34-next.1 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 94f9628c5b..3bdebf6052 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo-backend", "description": "A Backstage backend plugin that lets you browse TODO comments in your source code", - "version": "0.1.34-next.1", + "version": "0.1.34-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index 26080264ec..b8ede33b81 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-todo +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.12-next.1 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index d360992e04..7b4fbe7fc6 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.2.12-next.1", + "version": "0.2.12-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 09c44c7878..24c8d7592c 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-user-settings-backend +## 0.1.1-next.2 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 377b8bfe4f..8d8c5cf36f 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings-backend", "description": "The Backstage backend plugin to manage user settings", - "version": "0.1.1-next.1", + "version": "0.1.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 357f865b9d..f34a5ddc72 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-user-settings +## 0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.5.0-next.1 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 439c05cd6f..4ef48fad79 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.5.0-next.1", + "version": "0.5.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/vault-backend/CHANGELOG.md b/plugins/vault-backend/CHANGELOG.md index ef09111b07..925c755725 100644 --- a/plugins/vault-backend/CHANGELOG.md +++ b/plugins/vault-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-vault-backend +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index ff4d173cef..2e12305863 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault-backend", "description": "A Backstage backend plugin that integrates towards Vault", - "version": "0.2.3-next.1", + "version": "0.2.3-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/vault/CHANGELOG.md b/plugins/vault/CHANGELOG.md index 2bdb197722..5897543d2d 100644 --- a/plugins/vault/CHANGELOG.md +++ b/plugins/vault/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-vault +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/vault/package.json b/plugins/vault/package.json index 17ee68761d..c9c46b52f4 100644 --- a/plugins/vault/package.json +++ b/plugins/vault/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault", "description": "A Backstage plugin that integrates towards Vault", - "version": "0.1.4-next.1", + "version": "0.1.4-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index f231d3d77e..c10818bf77 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-xcmetrics +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.30-next.1 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index b1e2fe1475..cc88021ccb 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-xcmetrics", "description": "A Backstage plugin that shows XCode build metrics for your components", - "version": "0.2.30-next.1", + "version": "0.2.30-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", From 818bcdee97653868933725aa1e265499f74a92a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 16:40:46 +0000 Subject: [PATCH 138/160] Update dependency @pmmmwh/react-refresh-webpack-plugin to v0.5.8 Signed-off-by: Renovate Bot --- yarn.lock | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c369f3d91..ce6f91df32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11813,12 +11813,12 @@ __metadata: linkType: hard "@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7": - version: 0.5.7 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7" + version: 0.5.8 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.8" dependencies: ansi-html-community: ^0.0.8 common-path-prefix: ^3.0.0 - core-js-pure: ^3.8.1 + core-js-pure: ^3.23.3 error-stack-parser: ^2.0.6 find-up: ^5.0.0 html-entities: ^2.1.0 @@ -11829,7 +11829,7 @@ __metadata: "@types/webpack": 4.x || 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 - type-fest: ">=0.17.0 <3.0.0" + type-fest: ">=0.17.0 <4.0.0" webpack: ">=4.43.0 <6.0.0" webpack-dev-server: 3.x || 4.x webpack-hot-middleware: 2.x @@ -11847,7 +11847,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 3490649181878cc8808fb91f3870ef095e5a1fb9647b3ac83740df07379c9d1cf540f24bf2b09d5f26a3a8c805b2c6b9c5be7192bdb9317d0ffffa67426e9f66 + checksum: 48d8b2813dfba7d482e58a2b0161b79e3a5d608603f1a3c34d709ecc2e6e08f8b14f79934c57849d06f153eb327f18e3d8e1539f978e40ca91539c342f27b8ae languageName: node linkType: hard @@ -18850,13 +18850,20 @@ __metadata: languageName: node linkType: hard -"core-js-pure@npm:^3.20.2, core-js-pure@npm:^3.8.1": +"core-js-pure@npm:^3.20.2": version: 3.21.1 resolution: "core-js-pure@npm:3.21.1" checksum: 00a5dff599b7fb0b30746a638b9d0edbdc0df24ed1580ca56be595fbe3c78c375d37fc4e1bff23627109229702c9ee8ea2587a66b8280eb33b85160aa4e401e9 languageName: node linkType: hard +"core-js-pure@npm:^3.23.3": + version: 3.25.5 + resolution: "core-js-pure@npm:3.25.5" + checksum: e48799a8ab28f00ef3db18377142ff2c578574ab2b18ebddde6cbf12823e0181a57c80e3caa6046ce2a2e439d603a252be767583ddc54248e3d1060bc5012127 + languageName: node + linkType: hard + "core-js-pure@npm:^3.6.5": version: 3.25.3 resolution: "core-js-pure@npm:3.25.3" From 6e36dacfada6af00c9c30e3a53a0f6bfe8b706d7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 18:52:21 +0000 Subject: [PATCH 139/160] Update dependency @rjsf/utils to v5.0.0-beta.11 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c369f3d91..3955966e55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12007,8 +12007,8 @@ __metadata: linkType: hard "@rjsf/utils@npm:^5.0.0-beta.10": - version: 5.0.0-beta.10 - resolution: "@rjsf/utils@npm:5.0.0-beta.10" + version: 5.0.0-beta.11 + resolution: "@rjsf/utils@npm:5.0.0-beta.11" dependencies: json-schema-merge-allof: ^0.8.1 jsonpointer: ^5.0.1 @@ -12017,7 +12017,7 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^16.14.0 || >=17 - checksum: df5dda5a17ce11df2342ceff8d6ad2b3dad3a1f98d44aa7da378e6cd48387e4af023964b6143836e1ad16ff68f6592c0dcf24ff9f3fc859480b42b2c879406bc + checksum: 4e77616023b5cf193e1631a4d5fb8d54122d9f48e30ce2fcb1b22f057e2dcf37aae174f25f1afddb664b0114e461f9d738fa282995b07e9e3eb30f7469d77145 languageName: node linkType: hard From 51046b58b039720168eaa153784ac3d36ff978cf Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 7 Oct 2022 14:22:02 +0200 Subject: [PATCH 140/160] feat(catalog/github): use schedule from config at backend module Also, it removed `GithubEntityProviderCatalogModuleOptions` in favor of config-only for the backend module setup like at other similar modules. Signed-off-by: Patrick Jungermann --- .changeset/old-frogs-invent.md | 9 ++ .../api-report.md | 8 +- .../src/index.ts | 9 +- .../GithubEntityProviderCatalogModule.test.ts | 84 +++++++++++++++++++ .../GithubEntityProviderCatalogModule.ts} | 26 ++---- 5 files changed, 103 insertions(+), 33 deletions(-) create mode 100644 .changeset/old-frogs-invent.md create mode 100644 plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts rename plugins/catalog-backend-module-github/src/{module.ts => service/GithubEntityProviderCatalogModule.ts} (67%) diff --git a/.changeset/old-frogs-invent.md b/.changeset/old-frogs-invent.md new file mode 100644 index 0000000000..b1e77fa369 --- /dev/null +++ b/.changeset/old-frogs-invent.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Use schedule from config at backend module. + +Also, it removes `GithubEntityProviderCatalogModuleOptions` +in favor of config-only for the backend module setup +like at other similar modules. diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 86fc94a532..4ab534e6d3 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -20,7 +20,6 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; // @public export class GithubDiscoveryProcessor implements CatalogProcessor { @@ -68,14 +67,9 @@ export class GitHubEntityProvider implements EntityProvider { // @alpha export const githubEntityProviderCatalogModule: ( - options?: GithubEntityProviderCatalogModuleOptions | undefined, + options?: undefined, ) => BackendFeature; -// @alpha -export type GithubEntityProviderCatalogModuleOptions = { - schedule?: TaskScheduleDefinition; -}; - // @public (undocumented) export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { constructor(options: GitHubLocationAnalyzerOptions); diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index ca7c72f11b..3d7a7a2b4e 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -20,14 +20,13 @@ * @packageDocumentation */ +export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; +export type { GitHubLocationAnalyzerOptions } from './analyzers/GitHubLocationAnalyzer'; +export type { GithubMultiOrgConfig } from './lib'; export { GithubDiscoveryProcessor } from './processors/GithubDiscoveryProcessor'; export { GithubMultiOrgReaderProcessor } from './processors/GithubMultiOrgReaderProcessor'; export { GithubOrgReaderProcessor } from './processors/GithubOrgReaderProcessor'; export { GitHubEntityProvider } from './providers/GitHubEntityProvider'; export { GitHubOrgEntityProvider } from './providers/GitHubOrgEntityProvider'; export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntityProvider'; -export type { GithubMultiOrgConfig } from './lib'; -export { githubEntityProviderCatalogModule } from './module'; -export type { GithubEntityProviderCatalogModuleOptions } from './module'; -export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; -export type { GitHubLocationAnalyzerOptions } from './analyzers/GitHubLocationAnalyzer'; +export { githubEntityProviderCatalogModule } from './service/GithubEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..d65eb98661 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { githubEntityProviderCatalogModule } from './GithubEntityProviderCatalogModule'; +import { GitHubEntityProvider } from '../providers/GitHubEntityProvider'; + +describe('githubEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'module-test', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [githubEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'github-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-github/src/module.ts b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts similarity index 67% rename from plugins/catalog-backend-module-github/src/module.ts rename to plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts index 0391855618..c63e8ba7da 100644 --- a/plugins/catalog-backend-module-github/src/module.ts +++ b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts @@ -21,18 +21,8 @@ import { loggerServiceRef, schedulerServiceRef, } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; -import { GitHubEntityProvider } from './providers/GitHubEntityProvider'; - -/** - * Options for {@link githubEntityProviderCatalogModule}. - * - * @alpha - */ -export type GithubEntityProviderCatalogModuleOptions = { - schedule?: TaskScheduleDefinition; -}; +import { GitHubEntityProvider } from '../providers/GitHubEntityProvider'; /** * Registers the GitHubEntityProvider with the catalog processing extension point. @@ -42,25 +32,19 @@ export type GithubEntityProviderCatalogModuleOptions = { export const githubEntityProviderCatalogModule = createBackendModule({ pluginId: 'catalog', moduleId: 'githubEntityProvider', - register(env, options?: GithubEntityProviderCatalogModuleOptions) { + register(env) { env.registerInit({ deps: { - config: configServiceRef, catalog: catalogProcessingExtensionPoint, + config: configServiceRef, logger: loggerServiceRef, scheduler: schedulerServiceRef, }, - async init({ config, catalog, logger, scheduler }) { - const scheduleDef = options?.schedule ?? { - frequency: { seconds: 600 }, - timeout: { seconds: 900 }, - initialDelay: { seconds: 3 }, - }; - + async init({ catalog, config, logger, scheduler }) { catalog.addEntityProvider( GitHubEntityProvider.fromConfig(config, { logger: loggerToWinstonLogger(logger), - schedule: scheduler.createScheduledTaskRunner(scheduleDef), + scheduler, }), ); }, From 891a7be763033f4abb5eac8927e2bc6e49f8d180 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:56:18 +0000 Subject: [PATCH 141/160] Update dependency @rjsf/validator-ajv8 to v5.0.0-beta.11 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c369f3d91..ee24f2759a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12022,8 +12022,8 @@ __metadata: linkType: hard "@rjsf/validator-ajv8@npm:^5.0.0-beta.10": - version: 5.0.0-beta.10 - resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.10" + version: 5.0.0-beta.11 + resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.11" dependencies: ajv: ^8.11.0 ajv-formats: ^2.1.1 @@ -12031,7 +12031,7 @@ __metadata: lodash-es: ^4.17.15 peerDependencies: "@rjsf/utils": ^5.0.0-beta.1 - checksum: faa359c9ec58cf0fff35932d4e08a5c42f8449120cae606ebff108814f974e8db8a62df85e90e1fec7b0345388df09b83359ae1cc09103d41e73f298472b846a + checksum: e9aec2e77c69ee55fd35cc82b2c3fe0c78cf021145500bc86f7e0a5bae794163dfae198df0543ce635e22cd4235880dcbb7c73487ae0baae6b03e1bdea2955c4 languageName: node linkType: hard From 1df4478922e0b6824ee949f11bcec3283ae15aa0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 22:06:00 +0000 Subject: [PATCH 142/160] Update dependency @swc/helpers to v0.4.12 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c369f3d91..b611afd4be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12741,11 +12741,11 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.4.7": - version: 0.4.11 - resolution: "@swc/helpers@npm:0.4.11" + version: 0.4.12 + resolution: "@swc/helpers@npm:0.4.12" dependencies: tslib: ^2.4.0 - checksum: 736857d524b41a8a4db81094e9b027f554004e0fa3e86325d85bdb38f7e6459ce022db079edb6c61ba0f46fe8583b3e663e95f7acbd13e51b8da6c34e45bba2e + checksum: 3f9112f37d87815b6d4270137fc78d22bb98c75138e9b0eac7cac203ec2cf2bffbf13b20a713067c292affd5e9e70a724eb245b8daf0963e7fe528b901771c28 languageName: node linkType: hard From 068ba1a086af2d8e8d8750718adaecdc1865df8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 01:18:45 +0000 Subject: [PATCH 143/160] Update dependency @uiw/react-codemirror to v4.12.4 Signed-off-by: Renovate Bot --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 508ab48082..443464a659 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15172,9 +15172,9 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.12.3": - version: 4.12.3 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.12.3" +"@uiw/codemirror-extensions-basic-setup@npm:4.12.4": + version: 4.12.4 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.12.4" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/commands": ^6.0.0 @@ -15191,19 +15191,19 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 14c5c6694b2f5ce34bba2ed3fa7017a268c36f3ba234711580d31243603a14adc2b31ebec7d665cd6687917a8ac617d6ac453861d32bf277f43b65946ac1c656 + checksum: 27c65e051383cedf0ea63b3c923caf10aa45dba7cec01b3a767d81d1d0114ba221665bf0d8166f8de67946806fde7348768cf4a7683a70907e9f2eef9c99dbea languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.12.3 - resolution: "@uiw/react-codemirror@npm:4.12.3" + version: 4.12.4 + resolution: "@uiw/react-codemirror@npm:4.12.4" dependencies: "@babel/runtime": ^7.18.6 "@codemirror/commands": ^6.1.0 "@codemirror/state": ^6.1.1 "@codemirror/theme-one-dark": ^6.0.0 - "@uiw/codemirror-extensions-basic-setup": 4.12.3 + "@uiw/codemirror-extensions-basic-setup": 4.12.4 codemirror: ^6.0.0 peerDependencies: "@babel/runtime": ">=7.11.0" @@ -15213,7 +15213,7 @@ __metadata: codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 504003e6162dbe672072ace980117d1eeb9264282d6ad314de0398e0d29925133bc8d8c484c3aadb91b6ebedb8ebb259307b838cc31d39ab4792af5ab061949c + checksum: e7228beb95e918929e99e819c4f3b3920a16c5aca2477775c41bd16166af5bb7b5cefe240bd75b80fd9f61cde82086f93f8cd2dc1e2a4ecf02ff6d756cfd6f42 languageName: node linkType: hard From 21ac161d3b5030657c4fbcfd489d96d8dc107c9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 03:01:25 +0000 Subject: [PATCH 144/160] Update dependency commander to v9.4.1 Signed-off-by: Renovate Bot --- yarn.lock | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index ff6a24d46f..2594dc68fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18438,7 +18438,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:*, commander@npm:^9.1.0, commander@npm:^9.3.0": +"commander@npm:*, commander@npm:^9.3.0": version: 9.4.0 resolution: "commander@npm:9.4.0" checksum: a322de584a6ccd1ea83c24f6a660e52d16ffbe2613fcfbb8d2cc68bc9dec637492456d754fe8bb5b039ad843ed8e04fb0b107e581a75f62cde9e1a0ab1546e09 @@ -18494,6 +18494,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^9.1.0": + version: 9.4.1 + resolution: "commander@npm:9.4.1" + checksum: bfb18e325a5bdf772763c2213d5c7d9e77144d944124e988bcd8e5e65fb6d45d5d4e86b09155d0f2556c9a59c31e428720e57968bcd050b2306e910a0bf3cf13 + languageName: node + linkType: hard + "common-ancestor-path@npm:^1.0.1": version: 1.0.1 resolution: "common-ancestor-path@npm:1.0.1" From 48d8b3dcd44b0b18ec030188ebaeb9a4e397abb8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 09:05:46 +0000 Subject: [PATCH 145/160] Update dependency eslint-plugin-react to v7.31.10 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a56fc102ec..c77c9b8500 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21766,8 +21766,8 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.28.0": - version: 7.31.8 - resolution: "eslint-plugin-react@npm:7.31.8" + version: 7.31.10 + resolution: "eslint-plugin-react@npm:7.31.10" dependencies: array-includes: ^3.1.5 array.prototype.flatmap: ^1.3.0 @@ -21785,7 +21785,7 @@ __metadata: string.prototype.matchall: ^4.0.7 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 0683e2a624a4df6f08264a3f6bc614a81e8f961c83173bdf2d8d3523f84ed5d234cddc976dbc6815913e007c5984df742ba61be0c0592b27c3daabe0f68165a3 + checksum: f013669c296483559a760648fa06425f161b1aff93c668f14c4561c933d22a7836b745b88a795c53cab929c71513d5fd1f2ffdddff915709f01b77ac25f5b71b languageName: node linkType: hard From f80444ace591bc4ba249a1c7d8a085a080a6a8d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 09:29:15 +0000 Subject: [PATCH 146/160] Update dependency core-js to v3.25.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 7e959f5067..dba7aee112 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18887,9 +18887,9 @@ __metadata: linkType: hard "core-js@npm:^3.6.5": - version: 3.25.3 - resolution: "core-js@npm:3.25.3" - checksum: 26ca0a5e575e6da03dc30099f48bc00db50a1c0a3f81299165f111cb163869ae453de5e345f2cb7bc8be85a9adcee11fca94f0fbdbdf6d483c68e1c55a9efc5a + version: 3.25.5 + resolution: "core-js@npm:3.25.5" + checksum: 208b308c49bc022f90d4349d4c99802a73c9d55053976b3c529f10014c1e37845926defad8c519f2c7f71ea0acf18d2b323ab6aaee34dc85b4c4b3ced0623f3f languageName: node linkType: hard From fb149c087f3e53ed703f55a67312c76d388c3c54 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 12 Oct 2022 10:40:35 +0100 Subject: [PATCH 147/160] changesets: exit prerelease Signed-off-by: Patrik Oldsberg --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 33b9221a6a..dafb8797e2 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "example-app": "0.2.75", From 5c895c7c0c156c4bd43aa5f5a158aba5c990742d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 10:11:48 +0000 Subject: [PATCH 148/160] Update dependency marked to v4.1.1 Signed-off-by: Renovate Bot --- yarn.lock | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 9be7d5718e..ff45cdd46d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28795,7 +28795,7 @@ __metadata: languageName: node linkType: hard -"marked@npm:^4.0.10, marked@npm:^4.0.14": +"marked@npm:^4.0.10": version: 4.1.0 resolution: "marked@npm:4.1.0" bin: @@ -28804,6 +28804,15 @@ __metadata: languageName: node linkType: hard +"marked@npm:^4.0.14": + version: 4.1.1 + resolution: "marked@npm:4.1.1" + bin: + marked: bin/marked.js + checksum: 717e3357952ee53de831bf0eb110ed075bebca2376c58bcdf7ee523ef540d45308ad6d51b2c933da0968832ea4386f31c142ca65443e77c098e84f6cce73e418 + languageName: node + linkType: hard + "matcher@npm:^3.0.0": version: 3.0.0 resolution: "matcher@npm:3.0.0" From 5da539a0ee823dd20362babd301986e2f3c043d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 10:12:46 +0000 Subject: [PATCH 149/160] Update dependency minimist to v1.2.7 Signed-off-by: Renovate Bot --- yarn.lock | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 9be7d5718e..42f2b540ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29713,13 +29713,20 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6": version: 1.2.6 resolution: "minimist@npm:1.2.6" checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb languageName: node linkType: hard +"minimist@npm:^1.2.5": + version: 1.2.7 + resolution: "minimist@npm:1.2.7" + checksum: 7346574a1038ca23c32e02252f603801f09384dd1d78b69a943a4e8c2c28730b80e96193882d3d3b22a063445f460e48316b29b8a25addca2d7e5e8f75478bec + languageName: node + linkType: hard + "minimisted@npm:^2.0.0": version: 2.0.1 resolution: "minimisted@npm:2.0.1" From 2142f91b359048fd9319ebf72e918b72a6ecb7c6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 12 Oct 2022 11:48:45 +0100 Subject: [PATCH 150/160] techdocs-node: tweak mock-fs setup Signed-off-by: Patrik Oldsberg --- .../techdocs-node/src/stages/publish/googleStorage.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts index 896e528644..4418e00c81 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts @@ -219,13 +219,13 @@ describe('GoogleGCSPublish', () => { }, }; - beforeAll(() => { + beforeEach(() => { mockFs({ [directory]: files, }); }); - afterAll(() => { + afterEach(() => { mockFs.restore(); }); From ec9ee4741958ee1959980afd93679b5d71d4e8c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 11:11:56 +0000 Subject: [PATCH 151/160] Update dependency msw to v0.47.4 Signed-off-by: Renovate Bot --- yarn.lock | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index e5c64e6afd..d1ce206caa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30017,8 +30017,8 @@ __metadata: linkType: hard "msw@npm:^0.47.0": - version: 0.47.3 - resolution: "msw@npm:0.47.3" + version: 0.47.4 + resolution: "msw@npm:0.47.4" dependencies: "@mswjs/cookies": ^0.2.2 "@mswjs/interceptors": ^0.17.5 @@ -30037,7 +30037,7 @@ __metadata: outvariant: ^1.3.0 path-to-regexp: ^6.2.0 statuses: ^2.0.0 - strict-event-emitter: ^0.2.0 + strict-event-emitter: ^0.2.6 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: @@ -30047,7 +30047,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 1be018c7b2eff982409967cccb5c604e45f65710ee9698bab57fbe794f8426d1a4d33e52b75ef395c6d226948c799241c7c2c7748ec4f5b741e7f25bcbafbd1e + checksum: 10ff632641d40384d6622abf4df6399e4ae649db0f676b5d1ee2d0a515ec96f33abe9d4fecba08cdba4b2e43255af419da9eefc020d40a7e10669d0906457197 languageName: node linkType: hard @@ -36635,6 +36635,15 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.2.6": + version: 0.2.7 + resolution: "strict-event-emitter@npm:0.2.7" + dependencies: + events: ^3.3.0 + checksum: 111691e7d3fce0810586ccd8e8234af883ad3b121ef69091c7e260c32299d1ba085a95238ad09b43478bc5e9e80370f2fcb8114716e343be6f44bfc08fab4142 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" From b842b55f4f2b628d75b57d3ae5a20fa618ddda35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 11:12:45 +0000 Subject: [PATCH 152/160] Update dependency passport-saml to v3.2.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 e5c64e6afd..fe95d1ad18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31645,8 +31645,8 @@ __metadata: linkType: hard "passport-saml@npm:^3.1.2": - version: 3.2.1 - resolution: "passport-saml@npm:3.2.1" + version: 3.2.2 + resolution: "passport-saml@npm:3.2.2" dependencies: "@xmldom/xmldom": ^0.7.5 debug: ^4.3.2 @@ -31655,7 +31655,7 @@ __metadata: xml-encryption: ^2.0.0 xml2js: ^0.4.23 xmlbuilder: ^15.1.1 - checksum: 7236c82f2b16240acc6e026cbe171e130e703d0e85c7f83733be0137218a62f61dee72d34cea00ea02a0db4b823497e574ef4b6585a316097b926eb760f90bb8 + checksum: 1ae16bb5df60f70ba822896a1d3b360466f87ef6b45e941d28a91c1f9a681752a040da9e8b02e06c665e9b7277dd862abb384eb5e8b10d3b9e5fdabaa68b1691 languageName: node linkType: hard From e65095abdcd2f05cf7eb852a1b907728347d846f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 12:04:55 +0000 Subject: [PATCH 153/160] Update dependency postcss to v8.4.17 Signed-off-by: Renovate Bot --- yarn.lock | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 0f345a2ec2..5112a9569e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32624,7 +32624,18 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.1.0, postcss@npm:^8.4.7": +"postcss@npm:^8.1.0": + version: 8.4.17 + resolution: "postcss@npm:8.4.17" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: a6d9096dd711e17f7b1d18ff5dcb4fdedf3941d5a3dc8b0e4ea873b8f31972d57f73d6da9a8aed7ff389eb52190ed34f6a94f299a7f5ddc68b08a24a48f77eb9 + languageName: node + linkType: hard + +"postcss@npm:^8.4.7": version: 8.4.16 resolution: "postcss@npm:8.4.16" dependencies: From e65f18e0a246d9f9980e342e5c6f9e860a2bba72 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Wed, 12 Oct 2022 15:35:03 -0500 Subject: [PATCH 154/160] Removed database step from docs Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- docs/assets/getting-started/portal.png | Bin 134243 -> 135544 bytes docs/assets/getting-started/wizard.png | Bin 20559 -> 40529 bytes docs/getting-started/index.md | 8 ++------ 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/assets/getting-started/portal.png b/docs/assets/getting-started/portal.png index f16ad447578e8dea7fa38153f0dabeb6b405e452..b3133f22789133a7dc89cf6206e781cf6982e41c 100644 GIT binary patch literal 135544 zcmZU)1y~!++CK~w_m(2XrD%abakt`HC{A#SI|O%YafjksptuEhFD}L1CBda=|Dn%w z&U>!!W3Rn3%kfLd z9isfoLP2T<$E?(+01L)1Iy(8~T)Kgw7U9XAlsYA|*6M*{WcEUk zas~ZQII1P|;|6}Zw;bB?`L|+Zv#FLZ#J^~*R=wGK@0cx{`^Am-2upZ7!|v2_g`=|f zOXU}@`qnh6c6#hWU6-~HjQ-~9q<|tE(}*Fy_ApO4M+F)&v%{zl!-RWF8w z+8@Bt|4Hq!>BqWz`Eu&adKUUb(@AqBhQB>(@k;ZtSD&l3wErG9ubVJD-FGul@KSa@ z?2&7jX~}+8RD@%Kl~Li~!z|$tVI_FjP7K@O;9kUs!lA%EU%~bdISBtbiUP`c@t-mx z_4A2hY7(-tuunA;Co?lUXA655U8Il3uuIKbe$sN$QdHnKvA1P4GPO50V|BN6cy0nG z=*|x-+M2l-QM%jO*g5mN3sL_)gCACYt_Dz3{yoLTT8LUpQH4^%-pP!To0W}~janF; zl9E!;$@DY7>IbQRo5TJSqPB2xao`64+}zw)-8fk7oy-C3e0+QWw)cSd?^$4HusD0z zxfr>#*g4bubCLgC=YyHEiIb&+i>19C<@0rojO|@rgs7>XJNob6Ki|{L-SYo>vUC3T zwqQ31cs>GPXJrHY_u8t!a8*k{=awgZ{zt>R!BED`NYfvN*BZg{$Y#b8<6B9ZEaaSOj zbtloifr-L&YHyt%Ctd%%vCX8#m;Mi3fyO-us0@mhawsJT-Pe!DH60h_v` z^fIIFjsdET4)#xTPYNYZ5?;;Yl(21I_}7$B=A$mzx!C3a3KwJ&dLe?k9Xy7vI<;^z2!4FTssgkuw&+>3;MYMUPcQvb^ zbR+aJ;Zh-gxaS5T5+h)-;&C@Z{j6$ux#?W+5AM%j4^N-Hrek-|18{78WLD`%SlZ|( zL53b&iUu63NL&W{uDX2HizHXSpdocSRfXcVP@|*pmigngL5b!l`_&p+-3G^?kqDk9 z5+=ME|8nT!ko;1S)HRaz@Zm?N{tiE;M6AXM5Srml&A#Vnzil(nhyAB4HE_Zs0 z7Rma4W?YIkqYVdv7Ms03*!ZBv_Iqnd&Je;r0sEcQN60^V5w2o&bvps)JKArVf=~v3Ff!n9cpeEXjpLf-Z`z zw|%;(1)&pusnPZ+t22F}76j`!0iZ>djPs$B9$EU{xrdAG(kGO@CIK28&H@|+Ri4GJ z=P-p-!`-ta5B?L)^C$NXQ+OGq81q{}IP^ZRV`sM;_j+XQF{;z77xE-`8!VBP^vkwa zQf(Joo67eSeIr@?pRh_@)no`{2;v)p3hWtB zNy6CfFVFc4%k;UBrR!XvUaNZD8KbgWVZLn)KQBb39)TFVe&LF5+ESP~davGUc!;=I zPN5{+sSyy)S++k5RZNsV_!y3K4v#4u*j2%8ZH9ERi+r&G23hKo&q~VIswzxUh*i>w zEd@PV((uK1_;he4OnYU|#BuoU94r=o^-(Fc`4j945nI)QCZA7*19hmoOaoqO<&1`} zG#`SBC|uP*x9M2QE0?wv#`|k@f95UOV@HzN!fQA)nJv>}#O*WvAl3>`pVd->brEsc zOXr4kvtP#}^-mzM-wm-dYp7ZxuuR-Z&Jv}(d50gF;I{Q0v(!^96-b~3=3NW)03|Uc z(P>j^=Tsn_E)SNc5e`|c8VR}fx&L`(Eleb#RCX&W(IH#1Xp)G6V3Nof%F1q|`d$U& zZKz_TEzN7I3>%U@)|WP~M!`R#483;iNw`zD_FnVtzYhiW|Hz`5`G}eRp>z-R#iV-@ zy?8)xBYQ!wMT1(YM)_T7z4aXOip^I>Sm%@#NE)P`qIv=NQxgX>w;Ooe(?eRgI9xfJ zUFcj=1R-I7L<5Nm3-H0GsqX{5aVXnVpTv5bHjVuDPvq>sNIV2xPn?)v7PR|Ns`Ef& z22;hrem^7y5#M8-;Pz&DAA3y>po`1y;uZ8#{Mz zc)ScNVw~ry9~s6B2b@v+;Vh!8#lh^G+#du7Ube@unS@@ghMbrfqMR?CQccPZyqF7k zD1dzh_$5T4KUUNz3tQa3s-j7O*o%cqp6Qw?MMK`K$--3E`_pYZAm~UDaL4%eM~<&D z-2J&vfF;lORh591YbcY*QC}z+D&dgw5bLr%2{LOj(Zef-F240C+2BzrE*G-N1U{yX zN_c0r;%UEaA<5A<-zL>Zw8a6xJD&?^)^nX&Xl-tXHsb0XbzPIhkL^x^s<#f;ZLcP; zEhd)H)dz|M+kC^thlmNASDYUz;#v)DbwxsQgb6MC>?mFo?wbaP=@@pTWWSd=t2Z?< zOtUq5B^NB#gPoAmP+p6Ztq8T^P71jSLF0ZwXPSUpH?~!9a)@L+lmh4y6I=K7vZ&W0Bbk7`6OiF>HMNr26XY1(kn zz>I;2U#HP;E4fPso8>J2DD=iV?yr+?G6SF?z9=TV95jar2qoU!@ z8vDiWBy$JFe|SZeD(Zb7A(CxF(%xFJ{F*xh0C5S!q>LZD`?;AT@`SAK0WPy;2HCdB zm7grLKZ+lY-(77K(DU*UR4rxyJM;SN_sA+cTxN)EK?!-8e$`CZC(= z+skk2m#45t1rB3uG$hF9ygpWN=lXbf9yNRwt^f^yq&%p2*EB5$_p&G%ptjhH zBv5OR!jZ4LJzbnia6tG?56Kvp?p(}1l5Nqp;5pQkJcP`)s;8^}o8n9(!OY!^+`Q5E zCQci=qNU@s0`s)IERt2}MRLWvAaR^uC-Jk0hv!vob8&c;g4KsbH;;D=Kh@}!Pru{0 zxUwYwPC%m8$#Lpq6#}(C^iz^&@*wbUXR#sQsYj>eQCyu&`XHcrC^GcXcRsb>b-nH5 zWRb3AlG|rAm?RK7dz@0zcth(P)0H{q8H&JfjfPYBJJ1nCx^ zm9hWXqtN;8{pw_-~@CpeMglZ{AHT{M&TSOn3Wi+*I-! z^mGd|s{->HRW-}l)%|X%B{rX2jO|aUJf;s(A4NRA6(9e6;_!OiQ6O#uaD_dlOp(_~)b#*FM;)0@Vt+p zzL1P5pl?Hw8^>CpHGg9!9SA-a&TzrXCDd@B_E%a)db5UDQye?|AXe*`+H#3k5UhK4 znnCQ5Xf1&s^ME^;Z4z{EeR-VUk=pu6I6wPg!(9`pgzp@FO%x$m6X{*nmA2?4rhzX3 z?_zqtz*YX1JhaqI@RPY$Y2X*}`M&vda6(O=3^Yhak-S zY))MIPiJ%@S14u%6MW{57rUc<+VkON)T~An(APH`1u+Oyv7ZImg~XMrpI09*X8-$F zH^l!WQq9%b*Z&Og&eOuQ>_|Mj3>WcB#rYq2rA^7qdZ~)8W1oAIT^S?8kgD$crz9i# zCB79%KI5HU9XN_}!=%MUtfSgDf;fYMt~wgkNys?T2YMIyD7tJVZ!5blj1CmkW$nj4 zB`vd`NKeFGoG^Xa(|)Ojn8QuZ-%cQIrI!eydIJv#Y-YB|!gyTXGD4%)JjA06xWGtL zC#0wJ=z2eR?m$k8&?J!-suF)yU=$-LeLwrIanjWGe!`bC7>CCtABC?c2mJ0NqAHP` zl?l}X1*IfRa%ute&Ru*8gTv0C>J`DkJJu?*N8@kz)b^KA6`l|mMtoJz{5rl!VR*Cs zr1%Danh0yldoh0-;F^L4ipDhx{4EE-mT-!ALGxAh$}56%1Xj77gXL`aAy2Ky4Bvwn zkQW4C-R`UrCe*_7IwBFK8jIIS8W8UHx^jukxC03Htq?w;zzL(V@BUq0;9#R zHib5m14sggmrjBL6W!(NdT_s{bb=IxsZc6(VLN0@>Q;^`+LR;tN0ZzBBx zn;wQue9Dv7s6U5?V`jGnvp>gsO;!Wpv=Oz6=QX2t6=8mtjN+O(*7ok~r9~#Lbw7zo z`zs0LpX4Uw6kAEMW#=;`$`tmP=|oFXYzl}kzo)YQe4QU{JUD23RNCg?iJ2a}Hyg$w zE;(1tB7EF=+wyzA4=h2(*RCqb5dD%&G6nU7$#_2ip|2_eXwc5TFCFvoJJI_{;2k3g zE`@xGe_00eK_sw9`xU#kp$h>jl=-$#0gp(Be?O0G6gET%d$o4+mOQtZ@vuJ$6ME3@DgW&I~bEp z*&8<&Eat1@wma@l5-)xC-Msz@&H?JHQ4CZkFJ_*v7B zLg|iyvOXwlFuSW?mf~#&y-9DYTA4P0vNI<4E#muBtzuojJRgK@< zfgHzO35z{x{*9ciZ_0ts@5GVCkd&CDFPBP$7ex3o`NZM!$HtT(;QY2y_83CJSwYmX`v5{<4B?!ntOq%k2H$7RLDSIg>IL0gYR60?;Wpyot4I7Qj=hd@F|akeXN)0&U}RjQGG(i@Km-%3!Z_@^>sfW{=x{;@VD1X~vxuU+ zoIJ4-DxpJGJlqA+rWf+qN-A0cREe09Q{h6Rwiu0M_|xe%JZze(PwR|%l4%n%OaqV! z9Lnd*-yxG7^P*s4s~as}RZgu$Pbb(3*iD@-s1mHLd|Y9YHqaGrldja}!1^rjC4P{e zjI>m>AsOA~**)fUu z{loU5wfB@iU*_>#89fn)nat~#D3I2;3Z99H9)>Oyvf`b)Z4}G}NRmr5qc-vAK`rIl zllu-=>T8%RGHpbHpNzRtR>(gp{f%De_}%%x6Z!-C1qt%sO4lvHk$*w;UI=p>&H$5Y z*=FBQWN%W$Jv*oKDXX*fn!bnxHJ&WKgo5X1N=(rC<_N|tafsNU;LzLulBTP;b&7&` zYSp)94ZQ;rAQ&39F?7(VK;)0) zt9~Oih=-n*@mU`GAZZ;uBKd4Oj|SNU4G6eeUoM?_VhX{lqwXehC7C*NX!2k@Iftwy zL7xs&-}VJ^_QznNW*96M7++AuPAv7Yt|?c;v0r>8UGCPVs^J_4rq5`La5m%S{QbUO zC+IZ>M&u6zg;`~SDi*dpsznW&joJ|eOIrI@bYP8x5qKrO@C;F@s$k(VlB?@i>?dV{ z!PAd887;pZ@$E%wIeoCFolHiuO$PmyY{~MZq%iYEQ&1C{cz3>jq9iIZ`(pSizl(Q) zR!&IzM;;LJ{k@@RuW$fh$@}NK8N#8fQrKY1pk= zA_mVj7uJva2`S->+@J2#Sq@TYi%CdBYltuc2L^%lktTJ1_e7!j*koKXX(bI5Z>~Q` zYKv34z7;w9D6!w;|EuLDsjubK@%*Zd%%cn}W1)vZWYGrtl@Bky zc@OgaKVf@P`#m$CT|@P7ccMbsXMdBL=KNH^dwcPWlW2C+v%ruVpFISQWJ)`E^HuS7 z#8MXjUyk#EZvvq9x}o`vmF^<63>Vi*BD@TPEnW0}^JgGmv~d>yyRKz&^Rmjdx=CK1 z-NB#Y{O-5j`QIfufw(S0JczUqwt7YTy~3q@GQz-?-6E<38*(74Km~TiaF1`)0bl#4 z4ILcF{&z>gY^s@nI-Coea1JuVOck>j9jCV@_qTOQp683#i2C#v0(BnbjthNu0I)vj zp8ZTNmv2gc0O9pouBuhR#CW~ zXV~`9*5uXpR!QE#l(f3DI>=|Mzeq6d*qpKY(YLDdj(Y+AHY14pZ zgrs+%1&P~8u66h5`*w)Qv==fYbcY1IS`E$F;iI=VwXmR|14wlS8UjBgkE6s=rwpEB zG__h#QM_mcE5{rU=7)IaZ=y~K42yXw;szhkO+L}$xgAots@8kX)%B@%W4{l)R#TR4i@sjplqdtx0}j-~&&}jIMx$a1kz)@* zB1fky%gt6(in<)BK_n>v*kFiZO>y{5>+2MZ@5t=o=l*e81b@CQM&7O0YKt%_J6tT1 zjtRYHJnjoU_~?=^A94BJ_pQ&4tbV|G#5qy!p|YXp_b*0k@sVk_gr28)MxJy$Afq0d zk3GrfM__}@`lgCOk@9(HwFdQD49szaC9;~1p~y^Yv@}Pq6Ky${ADX`o{EN&4>E5V| zM3(J;8Ill0tU=wv+@O?N5T_g#^Ch8&;aoU>7aq)pkhUZenfoX2))xjYm8HDLKC`Pm&+VtDRTS1`pf9Nhb4&T%)mZJH8QmmY8>9z57*i;j8>;8V+X6@^*)_HscD2h4n z_lf#Wz3Rv4;ReF{I=5(-!tbU3tn6Lda02gb;+pUH9z5OQw~nO?kC0kDUNwAxjQ>4lwwhN?ftQTny8;QE#7b1Bq4W6N z9$7%4c^=&0xdl;dSy-W|!B?)&2OH;jE_{n4noG$+#8!OGSqiN`n9i4~Ok>Os1H(JkNA3oo#D{ zZhNnYCx&5PkCY>-&2k;%QlrP`+4gzVdd`ey4AXZRPbv!7>$T!#w=w=|6WL$72P46#@~au;(DLVLRV4>3{uFj!iOmiR61;Oi zi;KXnzWideWYJt_?=-oJHo&0v`c3ncuHL{zq2%>s0*ot}R7^vp!wA+jY>~LYZ>e~l zYhV*qDuFqM|0rpK8jVfh-4k5pBV0Lj(O2K^W8h@fT>2y>+4K7(?M0_Ylv9Z4p_1?B z7GnA`MQZWEeaLT0N}Il~xftiZl-d0-Fg_7V=0I`hJAvd#HNh6OKyl6SDdHJ3fMXv? zci;vzOE9)~Ff~u8qi;4CC|ylDHvYv3zA?lw$w3;vgyh}}CdLKU4j@!Ky?o?&u_cXG zf-!*goK{h36p!_MGjk#b_)gbgv6nR!Iw?1Nv=VG=)no>K9t(j}Z4&J!U@Dz|if(Nay;d>~NvZN_#onfYCY|~xQMCPvR zjq+(Y^eal^F2eh1_IK_Q`xSdd&phnT1jxvnVP*$iaci@53gzx#y}Mp$_vgYWvH3af zmvckA+~K}kPu=3-iIe7^@^6Y=j#B{ihmXJM zsC?&dQ0bJ|yQ{9Eb8F@MZJI^;tC`hH-K6JPn$yJdXfcQ|dJkz5_!5v7bqo`o2f)oZ z975phUb$6ouW>~oz-d^?Fx$Bf*xeDlvPs)B$g~s391GjnUSTk&b%<)5va(TJaH>P@mED#%^D&r&QoO-9oG4#VST_p8$5ZdMtmj({n+Sr5OiWS? z;xL1T;2U<_=`hjozy~8<&cno@=3lo{F_y+Xbe?J`nnH$17t4Ikifz=Z{n`{k!N0jH zT0WjvB_$RHfg_e0ddKmXE;a@)2omorT-XM$p80VRog#iYVH2hl&Wimcj z%HtPcKav`AmK@DQiuqEzrZB86oQ0w8+L^N=lgjKqE`|xA9Z!Z*gaL+h1>lR*BS#f$Ua!zGn zu5A{y z8H@E}erkLc*9dZLg{a&tDsz>|mQ#iaSh!oNI=dpa%FqQS$fe?y#r-^w&Sk{ru~F~$ z7Kd|(f02+VSsG-4Zg9A;N(|05#DL`2)9MekT&%Dux11~eC)Z1Y`3qY5hAF(vIUBUp z)R9m&G}l1*34A&+x0F@kMdoIq?c>zUo3KijyF-`PmyV5q^%>wh9OijDmXIzTI2E5~EI`Nrx!;V7dNqMfg%iwsw!m4dETB}G=4xoyFEc4hCI>PZa_ zFTpyeXXWt)+BKq$NI#AZcxhY&&v1zt)1w0TJ> zFXI7=#r;Ec1qy_3Sv!=%7W>m=x1n_;K!Y^D3D5g&uOHo2O9mq^=h#F_QExup$_wn8 zPosP_cXsegyqa)7>nr;0CpO1>MTRoS-GA@T|MJ=Vz$}wldOi(ZaifS;;^S80&yCKG z=cqlEWEzv%J_KIlH>LwvXQ8=eF;w|Fb&gEZJYfjf=h(IG@b5jHkVAd?-91LS3er?zNa zbN|Z0;+R}19MPcRX^q0MUGyT`niJ+n(3c=HxX}?&o>Ro|O=%+@`gl?flLLs$aV%YP zz$j}GY(7OzxJjUd4!0^QA{CLodsGjOqN9lMD5?qH4wMY&>^2R0*g6zC1uW4b%IT6! z@{#Cmf)K@5gcX%jlfkSJ5R_C+)M)%+8~Wh=q=|RiZEDzGHM@|`UkZl)Z1DD?SnhkFI=pP$zj@-d<9$q%}>OIgfD7=FS+A5mZl2uTWZUt z)I8{ZK7#@>z?904;Xx0Q#_MRgXWQ}a?lc~+e}F!BhTq^Y&O5L44no?AF!`U-yv{_0`K%$ zIqpIbvUT*o31hU6&+d46rvAijLorRzuyH=nD8fBV6wKkVvF~cPASiX6nU(ev1Z)aA zQIfg*C(pATL-?)r+Wd5rdG-5AsOPS0opIFD8e_hc=V!D5=_M7x_gsV79x>Ao)S-$h z6G!=L^f%A!0?=YqjV{DXZj3Mb~cCC8m28P}f)f$4F zH`+~aEkr?cJr?k%wadz{;*#o?CX++!+{>M~KZVXfNW3Mu9g2JnnW@BzN({ZjCoaCs z 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:pxEFv-Wp_v5k|#UdkOUfcFb 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

gE^A=*+uQT{Atq82 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ℑ+j8SaEY2kXTA>Oa_e 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 B-(jaB!iTr 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 Ti11GA06$}Wb3HQhbmad4>|=xw diff --git a/docs/assets/getting-started/wizard.png b/docs/assets/getting-started/wizard.png index 679878e252d7f2c8c47eedcf1d610c0a297d8cbb..8b6dce61310f0dfece37e617bc36d4c76914ad65 100644 GIT binary patch literal 40529 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 literal 20559 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 diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index ade73d3f68..477467bb88 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -51,14 +51,10 @@ create a subdirectory inside your current working directory. npx @backstage/create-app ``` -The wizard will ask you - -- The name of the app, which will also be the name of the directory -- The database type to use for the backend. For this guide, you'll be using the - SQLite option. +The wizard will ask you for the name of the app, which will also be the name of the directory

- Screenshot of the wizard asking for a name for the app, and a selection menu for the database. + Screenshot of the wizard asking for a name for the app.

### Run the Backstage app From 8f4f780c4908547a7b31b1e0b64994e83d99df7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 14:19:24 +0000 Subject: [PATCH 155/160] Update dependency @octokit/auth-app to v4.0.7 Signed-off-by: Renovate Bot --- yarn.lock | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3373e994b2..b078398626 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11178,20 +11178,20 @@ __metadata: linkType: hard "@octokit/auth-app@npm:^4.0.0": - version: 4.0.6 - resolution: "@octokit/auth-app@npm:4.0.6" + version: 4.0.7 + resolution: "@octokit/auth-app@npm:4.0.7" dependencies: "@octokit/auth-oauth-app": ^5.0.0 "@octokit/auth-oauth-user": ^2.0.0 "@octokit/request": ^6.0.0 "@octokit/request-error": ^3.0.0 - "@octokit/types": ^7.0.0 + "@octokit/types": ^8.0.0 "@types/lru-cache": ^5.1.0 deprecation: ^2.3.1 lru-cache: ^6.0.0 universal-github-app-jwt: ^1.0.1 universal-user-agent: ^6.0.0 - checksum: 342fece6db4470ee489e710af8aa14ffd3a89e666815a15fadbabe3a382932a714d5bece24375f0ca39f3310f9d10bd28bc348210282cbc6701d27dd9b2ff7ed + checksum: 880de7341f47c5a48822612f50ae453fb0990a188b29afdb2f80a1f7e703c88e068d05a4edc1aa9a8204c730b6f70cf2772fbd5da032fee4986a5266ebdda4c7 languageName: node linkType: hard @@ -11446,6 +11446,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^14.0.0": + version: 14.0.0 + resolution: "@octokit/openapi-types@npm:14.0.0" + checksum: 0a1f8f3be998cd82c5a640e9166d43fd183b33d5d36f5e1a9b81608e94d0da87c01ec46c9988f69cd26585d4e2ffc4d3ec99ee4f75e5fe997fc86dad0aa8293c + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^7.3.2": version: 7.4.0 resolution: "@octokit/openapi-types@npm:7.4.0" @@ -11669,6 +11676,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^8.0.0": + version: 8.0.0 + resolution: "@octokit/types@npm:8.0.0" + dependencies: + "@octokit/openapi-types": ^14.0.0 + checksum: 1a0197b2c4c522ac90f145e02b3f8cb048a47f71c2c6bdbf021a03db7dd30ca92a899c0186acb401337f218efe44e60d33cc1cc68715b622bb75bc1a4e79515d + languageName: node + linkType: hard + "@octokit/webhooks-methods@npm:^3.0.0": version: 3.0.0 resolution: "@octokit/webhooks-methods@npm:3.0.0" From 3a630161c4cb67cabae5181f817b856829217c85 Mon Sep 17 00:00:00 2001 From: marcofaggian Date: Fri, 14 Oct 2022 00:57:08 +0200 Subject: [PATCH 156/160] fix(api-docs): LANG=en_EN, to match "options" Signed-off-by: marcofaggian --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4270ef9824..c586b3a580 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "backstage-cli repo build --all", "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts", - "build:api-docs": "yarn build:api-reports --docs", + "build:api-docs": "LANG=en_EN yarn build:api-reports --docs", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false", "clean": "backstage-cli repo clean", From 77503b8f3641a1dfa1591e35ef397f61601702e1 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Fri, 14 Oct 2022 16:49:03 +0100 Subject: [PATCH 157/160] Increase memory for synk action for the snyk github issue sync workflow Signed-off-by: Harry Hogg --- .github/workflows/sync_snyk-github-issues.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 46a6227f2c..089474e5b8 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -13,6 +13,9 @@ jobs: matrix: node-version: [14.x] + env: + NODE_OPTIONS: --max-old-space-size=4096 + steps: - uses: actions/checkout@v3 From 746398082d6c5e3427cfa206013f78dc7031f440 Mon Sep 17 00:00:00 2001 From: Guillermo Manzo Date: Fri, 14 Oct 2022 09:36:33 -0700 Subject: [PATCH 158/160] Update ADOPTERS.md Signed-off-by: Guillermo Manzo --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index ca56a00890..574623cd14 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -22,7 +22,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | | [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | | [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [@guillermomanzo](https://github.com/guillermomanzo), [Sheena Sharma](mailto:shesharma@expediagroup.com), [@ajbw](https://github.com/ajbw) | EG Common Developer Toolkit | +| [Expedia Group](https://www.expediagroup.com) | [@gman0922](https://github.com/gman0922), [Sheena Sharma](mailto:shesharma@expediagroup.com), [Alekhya Karuturi](mailto:akaruturi@expediagroup.com) | EG Developer Front Door | | [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | | [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | | [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | From 98932961eaae429172e40f7dac731aaf0c04e8db Mon Sep 17 00:00:00 2001 From: Viachaslau Auchynnikau Date: Sat, 15 Oct 2022 16:40:56 +0300 Subject: [PATCH 159/160] fix(microsite): core-components docs layout fixed Signed-off-by: Viachaslau Auchynnikau --- .../src/components/LogViewer/LogViewer.tsx | 1 + .../src/components/MarkdownContent/MarkdownContent.tsx | 8 ++++---- .../components/ResponseErrorPanel/ResponseErrorPanel.tsx | 1 + .../src/components/TabbedLayout/TabbedLayout.tsx | 1 + .../src/components/WarningPanel/WarningPanel.tsx | 2 +- packages/core-components/src/layout/ItemCard/ItemCard.tsx | 1 + .../core-components/src/layout/ItemCard/ItemCardGrid.tsx | 1 + .../src/layout/ItemCard/ItemCardHeader.tsx | 1 + packages/core-components/src/layout/Sidebar/Items.tsx | 3 +++ .../core-components/src/layout/Sidebar/MobileSidebar.tsx | 1 + .../core-components/src/layout/Sidebar/SidebarGroup.tsx | 1 + .../src/layout/Sidebar/SidebarSubmenuItem.tsx | 1 + packages/core-components/src/layout/Sidebar/config.ts | 4 ++-- packages/core-components/src/layout/SignInPage/types.ts | 2 +- 14 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/core-components/src/components/LogViewer/LogViewer.tsx b/packages/core-components/src/components/LogViewer/LogViewer.tsx index bdb3973015..20530cffaf 100644 --- a/packages/core-components/src/components/LogViewer/LogViewer.tsx +++ b/packages/core-components/src/components/LogViewer/LogViewer.tsx @@ -44,6 +44,7 @@ export interface LogViewerProps { /** * A component that displays logs in a scrollable text area. * + * @remarks * The LogViewer has support for search and filtering, as well as displaying * text content with ANSI color escape codes. * diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index d97172bf75..425e723052 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -110,10 +110,10 @@ const components: Options['components'] = { }; /** - * MarkdownContent - * -- - * Renders markdown with the default dialect [gfm - GitHub flavored Markdown](https://github.github.com/gfm/) to backstage theme styled HTML. - * If you just want to render to plain [CommonMark](https://commonmark.org/), set the dialect to `'common-mark'` + * Renders markdown with the default dialect {@link https://github.github.com/gfm/ | gfm - GitHub flavored Markdown} to backstage theme styled HTML. + * + * @remarks + * If you just want to render to plain {@link https://commonmark.org/ | CommonMark}, set the dialect to `'common-mark'` */ export function MarkdownContent(props: Props) { const { diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx index 9600a3f179..f51c73195c 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx @@ -44,6 +44,7 @@ const useStyles = makeStyles( /** * Renders a warning panel as the effect of a failed server request. * + * @remarks * Has special treatment for ResponseError errors, to display rich * server-provided information about what happened. */ diff --git a/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx index 26e70e33b5..66a9b498cc 100644 --- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx +++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx @@ -71,6 +71,7 @@ export function createSubRoutesFromChildren( * TabbedLayout is a compound component, which allows you to define a layout for * pages using a sub-navigation mechanism. * + * @remarks * Consists of two parts: TabbedLayout and TabbedLayout.Route * * @example diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index 661bb1da22..2ddcef52ca 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -134,7 +134,7 @@ const capitalize = (s: string) => { }; /** - * WarningPanel. Show a user friendly error message to a user similar to + * Show a user friendly error message to a user similar to * ErrorPanel except that the warning panel only shows the warning message to * the user. * diff --git a/packages/core-components/src/layout/ItemCard/ItemCard.tsx b/packages/core-components/src/layout/ItemCard/ItemCard.tsx index e2efd0c136..fedcf7de8f 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCard.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCard.tsx @@ -40,6 +40,7 @@ type ItemCardProps = { * This card type has been deprecated. Instead use plain MUI Card and helpers * where appropriate. * + * @example * ``` * * diff --git a/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx b/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx index b27d224418..885b43ca34 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx @@ -49,6 +49,7 @@ export type ItemCardGridProps = Partial> & { * A default grid to use when arranging "item cards" - cards that let users * select among several options. * + * @remarks * The immediate children are expected to be MUI Card components. * * Styles for the grid can be overridden using the `classes` prop, e.g.: diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx index d9443ffc9a..76cf668079 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx @@ -64,6 +64,7 @@ export type ItemCardHeaderProps = Partial> & { * A simple card header, rendering a default look for "item cards" - cards that * are arranged in a grid for users to select among several options. * + * @remarks * This component expects to be placed within a MUI ``. * * Styles for the header can be overridden using the `classes` prop, e.g.: diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 5a0b92f558..4bc972e272 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -510,6 +510,7 @@ const SidebarItemWithSubmenu = ({ /** * Creates a `SidebarItem` * + * @remarks * If children contain a `SidebarSubmenu` component the `SidebarItem` will have a expandable submenu */ export const SidebarItem = forwardRef((props, ref) => { @@ -663,6 +664,8 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => { /** * A button which allows you to expand the sidebar when clicked. + * + * @remarks * Use optionally to replace sidebar's expand-on-hover feature with expand-on-click. * * If you are using this you might want to set the `disableExpandOnHover` of the `Sidebar` to `true`. diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index c85e6d11f2..bfa841690d 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -159,6 +159,7 @@ export const MobileSidebarContext = createContext({ /** * A navigation component for mobile screens, which sticks to the bottom. * + * @remarks * It alternates the normal sidebar by grouping the `SidebarItems` based on provided `SidebarGroup`s * either rendering them as a link or an overlay menu. * If no `SidebarGroup`s are provided the sidebar content is wrapped in an default overlay menu. diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx index 1158cc14df..cb9c786ea9 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx @@ -115,6 +115,7 @@ const MobileSidebarGroup = (props: SidebarGroupProps) => { /** * Groups items of the `Sidebar` together. * + * @remarks * On bigger screens, this won't have any effect at the moment. * On small screens, it will add an action to the bottom navigation - either triggering an overlay menu or acting as a link * diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index a1565a348f..51092d7e81 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -111,6 +111,7 @@ export type SidebarSubmenuItemDropdownItem = { /** * Holds submenu item content. * + * @remarks * title: Text content of submenu item * subtitle: A subtitle displayed under the main title * to: Path to navigate to when item is clicked diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index bb0ff207fb..dacf4bd0a5 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -20,13 +20,13 @@ const drawerWidthClosed = 72; const iconPadding = 24; const userBadgePadding = 18; -/** @public **/ +/** @public */ export type SidebarOptions = { drawerWidthClosed?: number; drawerWidthOpen?: number; }; -/** @public **/ +/** @public */ export type SubmenuOptions = { drawerWidthClosed?: number; drawerWidthOpen?: number; diff --git a/packages/core-components/src/layout/SignInPage/types.ts b/packages/core-components/src/layout/SignInPage/types.ts index 0909e7242d..8a0430a27d 100644 --- a/packages/core-components/src/layout/SignInPage/types.ts +++ b/packages/core-components/src/layout/SignInPage/types.ts @@ -32,7 +32,7 @@ export type SignInProviderConfig = { apiRef: ApiRef; }; -/** @public **/ +/** @public */ export type IdentityProviders = ('guest' | 'custom' | SignInProviderConfig)[]; export type ProviderComponent = ComponentType< From 0d0f30d87e4a2fcb230becde1f11a35742edcf52 Mon Sep 17 00:00:00 2001 From: Viachaslau Auchynnikau Date: Sat, 15 Oct 2022 17:18:31 +0300 Subject: [PATCH 160/160] fix(microsite): added changeset for core-components Signed-off-by: Viachaslau Auchynnikau --- .changeset/pretty-flies-agree.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pretty-flies-agree.md diff --git a/.changeset/pretty-flies-agree.md b/.changeset/pretty-flies-agree.md new file mode 100644 index 0000000000..da7ac4f6f5 --- /dev/null +++ b/.changeset/pretty-flies-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fixed layout for core-components docs where table was broken by splitting with code sample